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

  二、master管理多个worker进程

  在Nginx的配置文件中有个条目worker_processes,其用于指定master进程可以产生几个worker进程,默认情况下是CPU执行单元的数目。在Linux下实验发现,当kill掉worker进程的时候,master进程会自动再次启动worker进程,但是当kill掉master进程的时候,worker进程仍然活着并向外提供服务,这种方式或许是对于常驻服务最好的处理语义:master进程存在的时候会保证设定数目的工作进程存在,而master进程挂掉的时候worker进程仍然继续服务,不会存在单点故障导致服务立即停止的情况。

  其基本原理也很简单,这源于在Linux平台下,当子进程退出的时候,内核会向父进程发送SIGCHLD信号,父进程可以捕获这个信号,并通过wait系统调用搜集子进程退出的相关信息,此后子进程的资源会被相应的释放掉。因此,父进程可以通过接收信号的方式异步得到子进程退出的消息,并且适当安排创建工作者进程。

  当然,这仅仅是一个小trick,探究一下,发现Nginx的设计中,尤其是多进程服务端程序的开发维护中,大有学问可以借鉴!同时还有一个跟Nginx关系十分密切,估计也是使用相同master-worker方式构建的多进程的构架的,那就是php-fpm。之所以说关系密切,就是因为Apache本身支持php的解析,而Nginx只能通过外挂的方式,而挂件最常见的恰巧就是php-fpm了,通过ps查看,其也像是master-worker的结构,不过没看代码尚且不敢断定。

物联网

  2.1 跟踪环境的配置

  不知道啥时候,自己都快成了代码控了,GitHub上面一些感兴趣的项目代码都会clone下来并不断pull跟踪,nginx就是其中之一啊。调试环境设置很简单,只是有些点需要额外注意一下

[email protected]:~/nginx# apt-get install libpcre3-dev zlib1g-dev

[email protected]:~/nginx# auto/configure --with-debug

[email protected]:~/nginx# make

  上面configure的时候一定要添加–with-debug参数,这个时候可以让可执行程序支持生成debug的log信息,同时如果是MacOS的系统的话,还需要事先用homebrew安装gcc,然后添加–with-cc=/usr/local/bin/gcc-5指定使用gcc编译器(后面有时间说是要折腾一下Clang的,而苹果xcode默认就是用的这货),不过MacOS底层用的是kqueue而不是epoll,你应该知道我要说什么;make编译之后会在objs目录下面生成nginx可执行程序

[email protected]:~/nginx# mkdir logs

[email protected]:~/nginx# objs/nginx -p .

  通过-p参数,可以避免使用默认系统路径的权限问题,以及对现有环境的干扰。此时进程全部转到后台执行了,更要命的是IDE的调试环境此处被断开失连了,所以需要在nginx.c中将系统初始化过程的ngx_daemon()注释起来,就可以正常断点跟踪了。

  到此,Nginx的调试跟踪环境设置完成,设置conf/nginx.conf中log级别error_log logs/error.log debug;然后通过tail -f logs/error.log所有运行调试日志尽收眼底。

  2.2 多进程服务端程序设计

  通过官网Nginx文档大致了解了一下他的构架,看的真是让人拍案叫绝大快人心,请待我慢慢道来。

  2.2.1 多进程下的套接字

  传统上Nginx在启动开始的时候就bind一个地址进行listen,后续在fork()创建worker process的时候,这些进程是共享这个侦听套接字的,这个在linux fork()的手册中明确地被表示出了

  The child inherits copies of the parent’s set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. The child inherits copies of the parent’s set of open message queue descriptors, open directory streams.

  所以master process创建出来的所有worker process都是可以accept()客户端请求的,当多个进程对同一个socket调用accept()接收连接的时候,他们都会把自己放到这个套接字的等待队列上面去,然后一旦有客户发起连接请求,这个队列上面等待的进程就会被唤醒,这个过程在之前分析epoll的时候就介绍过了,但是在较早的epoll版本中,上面的唤醒过程会产生惊群(Thundering Herd)的问题:即使只有一个连接请求到来,也会唤醒在这个共享侦听套接字上所有等待的进程,而所有进程争抢这个连接只有一个能获得连接,其他所有进程都无功而返,所以新版的epoll添加了EPOLLEXCLUSIVE这么一个新的flag,通过在EPOLL_CTL_ADD的时候使用,保证在事件就绪的时候不会产生惊群的问题。