php-fpm 与 so_reuseport

问题:

如何让php-fpm能优雅地重启?

办法:

  1. 修改php-fpm listen的方式,开启so_reuseport
  2. 重启php-fpm时,先启动新进程,再停止旧进程

问题:

  1. 新进程和旧进程有不同的tcp backlog队列
  2. 系统会将新建立的连接平均分配到每个backlog队列中,新旧进程只能消费自己的backlog队列
  3. 新进程的所有子进程共享一个新的backlog队列,就进程的所有子进程共享一个旧的backlog队列
  4. 旧进程如何才能将backlog中的连接消费完?
  5. 旧进程如何阻止系统不要再分配连接到自己的backlog队列?
  6. 旧进程如何判断自己的backlog队列已空?
  7. 如果php-fpm进程通过exec、system、popen、proc_open等方式执行了外部的程序,则外部程序会继承所有的文件描述符(如果不做有意或无意的特殊处理的话,也会包括listen 9000 的文件描述符);问题在于,这个外部的程序虽然listen了9000端口,但是它绝对不会帮你处理9000端口的请求的,然后,系统分配过来的请求就会被阻塞

kill -SIGQUIT 可以解除子进程对9000端口的listen,却不能解除master进程对9000端口的listen

子进程的listen是继承父进程的

子进程接到SIGQUIT 信号后,close掉listen的文件描述符,然后创建了一个新的socket,如下:

(为什么要创建一个新的socket?)

同一个文件描述符被fork到多个子进程后,每个进程的关闭只会在本进程内关闭(让该资源和本进程脱离关系),然后减少该资源的引用计数,直到最后一个引用被解除,该资源才真正销毁

 

关于上述的问题7:

  1. 无意中对listen 9000的覆盖
    1. 当使用popen(“sleep” , “w”)的方式,就是以“写”的方式打开外部程序,则外部程序继承过来的标准输入0(原本listen 9000的文件描述符)会被覆盖
    2. 这时候就不会出现问题。 不过,你很少会如此打开一个外部程序的,常见的方式是以“读”的方式打开外部程序的,这时候,外部程序要覆盖掉的是文件描述符1,而0不会受影响
    3. 能否刻意覆盖掉文件描述符0呢? 比如: popen(“sleep”, “rw”) 读写方式打开?答案是:不可以; popen只允许单向打开(原因未深究,参考文档: http://php.net/manual/en/function.popen.php )
    4. 那么通过proc_open 实现呢?答案是: 可以; 如下:

      则sleep进程的文件描述符如下:

      这样就对了; 而且,proc_close() 可以获取外部进程的退出码

    5. 如果觉得proc_open 用起来比较麻烦,那么,或许可以给启动的外部程序来一个自己的shell,该shell负责关闭文件描述符0, 如: a.sh

      注意: 这个shell会存在一些问题,如下:

      改进如下

到现在为止,我们有能力不让外部的子进程来影响fpm请求的处理了;但是,还有一个listen 9000 但是不能处理请求的进程,那就是: php-fpm master进程

我们的处理方法可以如下:

  1. kill -SIGQUIT pid-of-php-fpm-master  && sleep 1 && kill -9 pid-of-php-fpm-master   (里面有 -9 ,好不和谐)

最后:

我们还没有解决如何让旧进程消费完旧进程的backlog中剩余的连接的问题,稍后研究。

分析:

socket中有一个shutdown的方法,可以关闭socket的读或(和)写,会触发关闭已建立的连接的方法,(对于listen的socket没有对端,想必不能用shutdown方法),测试发现:

  1. shutdown可以停止socket的listen,而不关闭文件描述符,确实可以实现关闭进程的listen,但是同时也会丢弃backlog中的连接,不管shutdown的是读还是写
  2. 测试脚本:

    (如果能通过单向关闭的方式通知内核不要再往该backlog分配连接不是很好吗?)

一种其它的实现方式:

在so_reuseport出现之前(包括现在)有一些程序是可以实现优雅重启的,原理就是,新旧进程时间继承lisen的文件描述符; 其实,继承文件描述符需要不小的工作量的。

如果能有一种标准的接口来处理继承文件描述符的事情,那么完全可以开发一个和应用无关的小程序,用来创建socket端口,然后遗传给真正要干活的应用程序,只需要遗传给应用程序的master进程就算OK了,如果需要重启程序,则只需要fork新的应用程序的master进程,然后发信号让旧的空闲的应用进程退出就可以了,这样就不会丢弃已创建的连接。

 

 

参考:

https://my.oschina.net/miffa/blog/390931

 

留下评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据