前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >想让服务器跑得快,并不是换个编程语言那么简单

想让服务器跑得快,并不是换个编程语言那么简单

作者头像
tyrchen
发布2018-03-28 15:28:45
9460
发布2018-03-28 15:28:45
举报
文章被收录于专栏:程序人生程序人生

最近一个读者问我:程序君,我是一个经常被你黑的phper,我想学一门新的语言,做服务器开发,看你好像用过好多语言,能推荐一个么?最好是开发效率高,支持并发,性能又好的。

看了这留言,程序君满脸黑线。真是冤枉啊,我和你萍水相逢,何来经常黑你?我只是偶尔调侃一下PHP而已,不敢黑任何一个phper,更不敢黑这「世界上最好的语言」呢。:)

言归正传。推荐一个开发效率高,并行性能好的语言/框架做服务器开发,这个问题有点太宽泛了:nodejs,go,elixir(erlang/otp),clojure,tornado(python)… 都不违背开发效率高,并行性能好的前提。可是:想让服务器跑得快,编程语言(及其虚拟机)本身真的能起决定性的作用么?

我们看看最基本的web server开发,毕竟,做互联网的,没人离得开web server。然而,我想大部分所谓的web程序员,没几个懂web server的。拜library/framework所赐,在他们眼里,web server就是十行代码搞定的玩意儿:

(nodejs的高逼格hello world:写一个web server)

library/framework是好东西,可以让你不要在意各种细节,大大降低了开发的门槛。但入了门之后,成了熟练工种,最好反过来仔细研究一下被封装掉的那些细节,否则层次永远停留在码工的水平。

Web server和任何TCP server一样,都是处理网络流量的,因此网络处理的效率便是web server效率的关键。除此之外:

  • HTTP Request报文的处理,尤其是header的处理(一个高效的parser)
  • URL dispatch(一个高效的pattern matching engine)
  • HTTP Response的封装(一个高效的string builder)

也是容不得半点马虎的效率杀手。我们主要看网络相关的,这个是最大头。

网络处理

首先需要考虑的是使用什么并发模型:threading? single event-loop? multiple event-loop?

早期的web server使用OS提供的thread/process(以下讲thread,并不区分thread/process),一个connection,accept下来之后,就交给一个thread去处理,thread内部可以做任意的blocking I/O,并发性能靠thread的数量来保证,这就是threading模型。在实际使用中,一般会预先创建(pre-fork)一个thread pool来重复利用空闲的thread,减少thread创建/终止的系统损耗。Apache就是典型的threading模型。

Threading模型的好处是代码很直观,符合我们思考问题解决问题的常用模式。操作系统会处理线程的调度,使其适应多核的场景。坏处是每个request的处理都涉及上下文切换(context switch),系统损耗大,同时thread的内存消耗(memory footprint)不小,很难创建成千上万个thread来有效的提供更多并发能力。

为了弥补threading模型的弱点,如今大多数web server采用的是event driven模型。很多人误以为event loop是nodejs首创,实际lighthttpd/nginx很早就采用了event driven模型。

event driven主要有两种使用场景:single event loop和multiple event loop。

所谓single event loop是指使用一个thread承载event loop,处理non blocking I/O。如图:

lighthttpd/nodejs/tornado采用这种方式。单线程模型简单,不必考虑跨线程同步(往往是locking),因为web server主要的处理瓶颈在网络的I/O,使用event handler来处理non blocking I/O后,单线程效率都相当高。不过,event handler的问题在于代码很不直观,原本顺序的函数调用如今都变成了一个个callback,逻辑一复杂,就遇上callback hell。

multiple event loop一般是在每个物理core上运行一个event loop,以最大程度压榨CPU资源。如图:

nginx/go 采用此种并发模型。其缺点也是代码不够直观,还要仔细处理跨线程同步。

在网络编程中,选取什么样的并发模型直接影响系统最终的效率。比如说,要达到每秒上百k请求的处理能力,threading模型肯定是无能为力的,用什么语言都没戏。

即便我们选取了看上去最好的mutilple event loop模型,在系统层面,需要优化的东西也不少:

  • 避免使用lock
  • 优化系统调用

避免使用lock是一个比较大的话题,多线程下内存分配都会触及locking(nginx由于是多进程,这点上就要优于一般而言是单进程多线程的go web server)。locking会把并发处理降级成单线程处理,极大损伤系统的速度。就分配内存而言,可以采取memory pool预分配的方式,由每个thread的event loop自己管理自己的memory poll里的内存的使用,这样就避免了大多内存分配的lock。其他用到lock的地方还有不少,不展开。

系统调用是另外一个很大的性能杀手。系统调用涉及用户态到内核态的切换,非常耗时,有些系统调用还涉及到locking,总之,能少触发系统调用就少触发。我们看看在浏览器请求 GET / HTTP/1.1 后,strace(linux下的system call/signal监控软件)监控到的nginx的系统调用:

我们看到,一个 GET / 触发了:

  • gettimeofday
  • accept4
  • epoll_ctl
  • epoll_wait
  • gettimeofday
  • recvfrom
  • stat
  • open
  • fstat
  • pread
  • writev
  • write
  • close

这么多系统调用。具体它们是干什么的就不解释了,感兴趣的可以自己研究。nginx是web server里面优化得很好的系统,一个简单的网页请求,竟然还要这么多系统调用。

正常我们accept一个connection,会使用 accept(),而nginx使用了 accept4(),这就是为了避免两个额外的 fctrl() 而做的优化。

发送文件为什么不用 sendfile()send(),而是 writev(),大概也有类似的考虑。

有的web server的实现,把http header和http body分开发,这样不但会多一个write的系统调用,还多了网络中的round trip(header, ack) - 要知道,一般的网络中,一个round trip,几十ms就过去了,这虽然在高并发下并不是太大的问题,只是处理的latency增加了而已,但在低并发下,performance就大打折扣。

如果不是做streaming(transfer-coding: chunked),则http header和http body分开发送没有任何必要。

除此之外,pread() 从磁盘上读取整个文件损耗不小,第一次读取后,应该缓存起来,下次相同用户再请求(浏览器一般会带If-Modified头),只要没改变,服务器应该返回 304 NOT MODIFIED;而其他用户请求这个文件,应该从内存中调用而非从磁盘读取等等。我们看同一个用户再访问这个文件时,nginx都做了什么:

最后,还有不少细节值得研究:我们知道,在http头里,服务器要返回当前时间。获取时间 gettimeofday() 这个系统调用消耗也不小,如果你的服务器每秒要处理10k个请求,每次都获取,意味着10k次系统调用。如果我们每秒定期调用一次 gettimeofday(),把结果缓存起来,可以减少99.999%的浪费。

不要小看这些优化,高并发情况下,即便多做一个动作要100us,10k乘上去,就是1s,吓人吧?

语言的影响

写一个web server这件事,时至今日,已经不用我们这些程序员去做了,因为,市面上已经存在太多太好的web server。但是作为一个服务器开发者,总还需要写点这样那样的TCP/UDP server。要能够高效处理,如同上文所述,需要的是对架构的熟悉水平,对操作系统的了解程度,对协议的掌握。这些是关键。语言只是给你一个更好的发挥空间。当然,如果能够做到对系统了如指掌,又对各个语言的优劣有有个基本了解,那就再好不过了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2015-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 网络处理
  • 语言的影响
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档