1

Nginx在web开发者眼中就是高并发高性能的代名词,其基于事件的架构也被众多开发者效仿。我从Nginx的网站找到一篇技术文章将Nginx是怎样实现的,文章是Nginx的产品老大Owen
Garrett在加入公司22个月时写的,深入简出。这篇博客后面的内容尽量保证是对原文的翻译,如果有个人理解或者延伸阅读我会加标“译注”。原文地址Inside NGINX: How We Designed for Performance & Scale

Nginx强劲的高性能表现来自其合理的软件设计。传统的web服务器和应用服务器架构设计上采用多进程或线程作为其处理业务的基本单位,而Nginx更多的使用了事件驱动的架构。正是这种架构使得Nginx可以轻松支持数十万的并发链接。【译注:Nginx相比其他的web服务器使用了更少的进程,将IO事件集中在固定的进程内处理,减少了很多系统开销,可以从下文理解到。】

The Inside NGINX infographic 较为清晰的讲诉了Nginx如何在一个进程内处理并发链接,下面我们深入看一下细节。

Nginx进程模型

clipboard.png

在讲设计实现之前,有必要先看一下Ngxin如何在linux之上运行的。Nginx启动会创建一个主进程(主管进程,负责读取配置、绑定端口、管理其他子进程)和一些worker进程和辅助进程。

# service nginx restart
# ps -ef --forest | grep nginx
root     32475     1  0 13:36 ?        00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx    32476 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32477 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32479 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32480 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32481 32475  0 13:36 ?        00:00:00  \_ nginx: cache manager process
nginx    32482 32475  0 13:36 ?        00:00:00  \_ nginx: cache loader process

这个示例运行在4核的server上,Nginx主进程创建4个worker进程和2个cahce辅助进程。

为什么架构很重要?

Unix应用程序的基本要素是进程或者线程。(Linux OS调度不区分进程还是线程,二者的最大区别在于它们对于memory的共享程度。)进程或线程是一个自包含的可以独立运行的任务,OS可以调度它到某个CPU核上执行。有很多复杂的应用程序运行在多进程或线程模式下是基于以下两点考虑:

  • 可以使用更多CPU资源。【译注:还有memory、IO等其他资源】

  • 可以轻松做到并行处理(比如,同时处理多个链接)。
    进程和线程都会消耗资源,需要占用memory和其他OS资源,并且在运行时还有context switch的系统开销。一般的server可以负担几百数量级的进程或者线程,当进程或线程数量继续上升到更高的数量级,memory消耗和IO阻塞引起的系统负荷会很高,使得应用程序运行比较低效。

在设计网络程序时,开发者会很自然的设计成每个进程或线程处理一个网络连接。这种架构比较简单容易实现,但是比较难以扩展,尤其是当网络连接增长到上千以后。

Nginx怎样工作的?

Nginx可配置数量的进程,推荐配置数量和CPU的核数量相当:

  • 主进程读配置,绑定端口,然后启动一定数量的子进程。

  • cache loader子进程在启动的时候运行,负责把硬盘上的数据搬进内存,然后就退出了。因为它是一次性的任务,系统开销很小。

  • cahce manager子进程启动后监控维护cache区。

  • worker子进程是真正处理业务的进程,负责处理网络连接,读写硬盘,跟上游server交互等等。
    Nginx推荐配置worker的数量跟CPU核数量线性关系,每个CPU核运行一个worker进程。可以通过配置 worker_processes auto来使用该推荐设置。

当Nginx server处理业务时,worker进程们是最繁忙的,每个worker通过非阻塞的IO复用方式处理很多连接,尽量减少不必要的上下文切换。
每个worker进程都是单线程的进程,接收连接上的request并处理后回应。进程间可以通过共享内存的方式进行进程间通信。

Nginx worker进程

clipboard.png

每个Nginx worker进程由主进程读取配置创建,通过accept_mutex竞争获得要listen的socket并加入自己的IO监听列表中。
每来一个新的连接都会触发新的事件,这些事件送给worker内的状态机来处理。(Nginx支持各种类型的状态机,如http/tcp/SMTP/IMAP/POP3等)。大部分的web server逻辑上都有这样的状态机,只是实现方式不一样。
clipboard.png

状态机调度

我们可以想象类比状态机是象棋游戏的规则。每个HTTP transaction(译注:一组的Http请求,可以对应成某个socket上发生的所有http请求)就是一个象棋游戏。对弈的一方是web server,可以类比为象棋大师。另一方为client,类比为象棋爱好者。
游戏的规则可以很复杂,比如web server需要跟其他application沟通协作完成业务处理,第三方的nginx模块甚至可以扩展规则。

阻塞式状态机

大多数的web服务器和应用程序使用每个连接对应一个进程或线程的模式来玩象棋游戏。每个进程或线程给一个client完成对弈直到游戏结束。在整个过程中,进程大部分时间都是处在阻塞状态--等待client完成下一步走棋。

clipboard.png

  1. web服务器主进程在服务端口上监听新的连接(客户端发起的新游戏的请求)。

  2. 有新的游戏请求时,主进程创建子进程负责完成跟客户端的对弈。主进程继续监听服务端口。

  3. 当游戏结束时,子进程要么等待client开始新游戏(通过keepalive机制保活一段时间连接)要么退出(keepalive超时后)。
    这种模型每玩一局server都要创建一个对应的进程来完成对弈。这种架构简单并且容易容易扩展新功能,但有些大炮打蚊子,杀鸡用牛刀的感觉。进程是个重器,系统开销比较大,而解决的问题是个轻量级的问题。容易编程实现但是浪费比较大。

Nginx才是真正的并发大师

你可能听说过一人同时对战多人的象棋大赛
clipboard.png

这就是Nginx worker进程的工作方式。每个worker进程(一般每个CPU核有一个worker进程)都是一个象棋大师,可以同时对弈数十万对手。

clipboard.png

  1. worker进程等待listen和connection sockets的事件。(译注:listen socket就是server用来监听新建连接的socket,connection socket是accept系统调用返回的新建socket,详细可参加accept的手册)

  2. 事件发生后,worker进程来处理这些事件:

    • listen socket的事件表示有新的客户端要开始新的游戏。worker通过accept()创建新的connection socket,并加入监听列表。

    • connection socket的事件表示客户端走了一步棋,worker进程可以做下一步应对。
      worker进程从不会在网络IO上阻塞,当它应对完客户端的走棋走出自己的一步后,可以马上应对下一个客户端的走棋或接收新的连接请求。

为什么这样做比阻塞式的多进程架构更快?

Nginx的worker进程很容易扩展支持数十万并发连接。每个新接入的连接只需要创建新的socket消耗少量的内存,每个连接的系统开销相对要比进程开销小很多。另外通过Nginx worker进程绑定CPU技术可以进一步减少上下文切换和cache失效等系统开销。
而阻塞式每个进程服务一个连接的方式,每个连接都会消耗很多资源,而且进程切换比较频繁导致系统开销比较大。
更详细的解释,可以参考这篇文章--Nginx架构,作者是Ngxin的VP和共同创始人,Andrew Alexeev.

更新配置和升级Nginx

Nginx的这种少量进程的架构使得更新配置和升级Nginx版本很容易。

clipboard.png

更新Ngxin配置是一件非常容易事情而且非常可靠。很简单的nginx -s reload就搞定了。运行这个命令实际上是给Nginx主进程发送了一个SIGHUP的信号,主进程收到该信号后做了两件事情:

  1. 重新加载配置并且根据新的配置创建一组新的worker进程,这些新的进程可以马上开始干活。

  2. 通知老的worker进程优雅地退出。
    重新装载的过程会引起短暂的CPU和内存的使用高峰,但这种影响总体来说比较微小,你甚至可以每秒多次做这个操作。

Nginx程序的升级就更加漂亮了,根本不会影响正在处理的连接,轻轻松松升级完成用户根本没有感觉。

clipboard.png

Ngxin程序升级跟更新配置相似。启动新的Nginx主进程,它会跟旧的主进程共享listen sockets。新的进程起来后,你可以发送信号给旧的进程退出。详细过程可以参看Controlling Nginx.

总结

The Inside NGINX infographic描述了Nginx的整体功能,其实它概括性描述的背后是Nginx开发人员十几年的创新和优化。如果你想了解更多,可以参看这些材料:
Installing and Tuning Nginx for Performance
Tuning Nginx for Performance
The Architecture of Open Source Applications – NGINX
Socket Sharding in NGINX Release 1.9.1 (using the SO_REUSEPORT socket option)


microelec
316 声望21 粉丝

通信老兵,从LTE无线通信到webRTC音视频,从基础设施到音视频应用。