浅谈多进程程序的控制和管理

  Nginx对于共享accept套接字惊群问题的处理,有三个方法:

  (1). accept_mutex = on

  当这个选项打开的时候,worker process在其任务循环的时候,会首先通过ngx_trylock_accept_mutex去获得一个进程间的ngx_accept_mutex互斥锁,而该锁通常是使用文件锁来实现的。在持有这个锁的时候,首先收集底层就绪的事件,同时执行accept的所有回调,然后释放该锁,处理一般的非accept事件。

  (2). accept_mutex = off

  这个设置在较新版本的Nginx已经是默认关闭的,主要考虑到的是:一来通过EPOLLEXCLUSIVE、下面的SO_REUSEPORT等新技术可以避免accept的时候惊群的问题;另一方面Nginx采用基于事件的处理方式,worker process只有很少的几个,而不像Apache的技术Prefork很多的子进程,所以即使发生惊群对系统造成的影响也极为有限。

  (3). reuseport

  在Linux内核3.9的时候,内核Socket支持了SO_REUSEPORT选项,而Nginx在1.9版本中引入了这个选项,这样每个worker process都可以同时侦听同一个IP:Port地址,内核会发现哪些listener可用,从而自动将连接请求分配给给定的worker process,消除了Nginx传统上通过用户态采用accept_mutex互斥锁而带来的性能损耗问题。

  上面三种方式的性能对比在官方也给出了 测试结果 。

  2.2.2 基于事件的异步模型

  异步模型是新一代http服务器Nginx和老牌Apache最大的不同之处:

  Apache采用的是Prefork技术,服务启动之后预先启动一定数目的子进程,当服务器压力增大的时候不断增加子进程的数目,而当服务器空闲后自动关闭一些子进程,虽然这种弹性常驻子进程比One Child per Client的模型要进步很多,但是经过这么久的多进程、协程开发技术的熏陶可知,子进程的增加只在一定范围内可以增加服务能力,同时子进程在进程切换、内存等方面会对服务器带来很大的压力,如果当连接客户达到C10K的时候其占用的资源是不可估量的。

  Nginx采用的是基于事件驱动的模型来解决C10K问题,所以通常Nginx只需要启动很少(通常CPU执行单元个数)的worker process就可以同时服务大量连接,以至于越来越多的http服务器迁移到Nginx平台上面。其工作流程主要是:

  当master process通过fork()创建出几个worker process的时候,worker process进程主执行函数为ngx_worker_process_cycle(),这里面除了检查各种状态标识(比如接受到父进程发送的信号后,设置ngx_terminate、ngx_quit、ngx_reopen等标识)作出特定行为外,其正事主要是通过ngx_process_events_and_timers处理事件:

  此时如果accept_mutex==on,而当ngx_trylock_accept_mutex抢锁失败则直接返回,否则就会设置NGX_POST_EVENTS这个标识,表示事件的回调延后执行。因为我们要把持锁的临界区降低,所以在持锁的过程中,通过ngx_process_events(实质乃是ngx_epoll_module_ctx.actions)检查底层侦听套接字就绪的事件,根据epoll特性可以快速的收集就绪事件并添加到ngx_posted_accept_events和ngx_posted_events队列上去,执行ngx_posted_accept_events队列回调后释放锁,最后执行一般的事件回调操作。

  如果accept_mutex==off,那么在ngx_process_events的过程中,事件的回调将会在搜集就绪事件的过程中同步执行。

  2.3 Nginx配置文件和二进制程序平滑升级

  Nginx中多进程之间将信号运用的活灵活现(Windows平台下没用借用信号的方式,而是用其特有的Event事件进行的通信),使得Nginx可以在不间断服务的情况下进行配置文件,甚至是二进制文件的平滑升级操作,信号的含义可以参见ngx_config.h,信号处理参见ngx_process.c:ngx_signal_handler,在信号处理文件中其实也只是设置一些状态变量,然后在进程的时间循环中去执行相应的操作,比如向worker process发送特定信号、启动worker process等。

  2.3.1 Nginx配置文件平滑升级

  通过nginx –s reload或者直接kill -SIGHUP向Nginx master process发送信号,当master process接受到SIGHUP信号的时候:

  a. 检查配置文件,然后打开新的listen socket和日志文件,如果失败则让old nginx继续执行,否则

  b. 创建新的worker process,同时向old worker process发送信息,让他们graceful关闭,old worker process会关闭侦听套接字,服务已经连接的客户,当所有连接客户服务完了之后退出

  2.3.2 Nginx二进制程序平滑升级

  将新的二进制文件拷贝覆盖原二进制执行文件,然后向master process发送SIGUSR2信号,当master process接收到该信号的时候: