一文吃透 PHP 进程信号处理
背景
前两周老大给安排了一个任务,写一个监听信号的包。因为我司的项目是运行在容器里边的,每次上线,需要重新打包镜像,然后启动。在重新打包之前,Dokcer会先给容器发送一个信号,然后等待一段超时时间(默认10s)后,再发送SIGKILL信号来终止容器
现在有一种情况,容器中有一个常驻进程,该常驻进程的任务是不断的消费队列里的消息。假设现在要上线,需要关杀掉容器,Docker给容器里跑的常驻进程发送一个信号,告诉它我10s后会将你关闭,假设现在已经过了9秒,常驻进程刚从队列中取出一条消息,1s内还没将后续逻辑执行完,进程就已经被杀了,此时这条消息就丢失了,且可能会产生脏数据
上边就是这次任务的背景,需要通过监听信号来决定后续如何操作。对于上边这种情况,当常驻进程收到Docker发送的关闭信号时,将该进程阻塞即可,一直sleep,直到杀掉容器。OK,清楚背景之后,下边就介绍一下PHP中的信号(后边会再整理一篇这个包如何写,并将包发布到https://packagist.org/,供需要的小伙伴使用)
一、在Linux操作系统中有哪些信号
1、简单介绍信号
信号是事件发生时对进程的通知机制,有时又称为软件中断。一个进程可以向另一个进程发送信号,比如子进程结束时都会向父进程发送一个SIGCHLD(17号信号)来通知父进程,所以有时信号也被当作一种进程间通信的机制。
在linux系统下,通常我们使用 kill -9 XXPID来结束一个进程,其实这个命令的实质就是向某进程发送SIGKILL(9号信号),对于在前台进程我们通常用Ctrl+c快捷键来结束运行,该快捷键的实质是向当前进程发送SIGINT(2号信号),而进程收到该信号的默认行为是结束运行
2、常用信号
下边这些信号,可以使用kill -l命令进行查看
下边介绍几个比较重要且常用的信号:
二、PHP中处理信号相关函数
PHP的pcntl扩展以及posix扩展为我们提供了若干操作信号的方法(若想使用这些函数,需要先安装这几个扩展)
下边具体介绍几个我在本次任务中用到的方法:
declare
declare结构用来设定一段代码的执行指令。declare的语法和其它流程控制结构相似
directive部分允许设定declare代码段的行为。目前只认识两个指令:ticks和encoding。declare代码段中的 statement部分将被执行——怎样执行以及执行中有什么副作用出现取决于directive中设定的指令
Ticks
Tick(时钟周期)是一个在declare代码段中解释器每执行N条可计时的低级语句就会发生的事件N的值是在declare 中的directive部分用ticks=N来指定的。不是所有语句都可计时。通常条件表达式和*参数表达式*都不可计时。在每个tick中出现的事件是由registertickfunction()来指定的,注意每个 tick 中可以出现多个事件
更详细的内容,可查看官方文档:https://www.php.net/manual/zh/control-structures.declare.php
pcntl_signal
pcntl_signal,安装一个信号处理器
函数pcntl_signal()为signo指定的信号安装一个新的信号处理器
posix_kill
posix_kill,向进程发送一个信号
第一个参数为进程ID,第二个参数为你要发送的信号
pcntlsignaldispatch
pcntlsignaldispatch,调用等待信号的处理器
函数pcntlsignaldispatch()调用每个等待信号通过pcntl_signal()安装的处理器
pcntlasyncsignals()
异步信号处理,用于启用无需 ticks (这会带来很多额外的开销)的异步信号处理。(PHP>=7.1)
三、PHP中处理信号量的方式
前边我们知道我们可以通过declare(ticks=1)和pcntl_signal组合的方式监听信号,即每一条PHP低级语句,就会检查一次当前进程是否有未处理的信号,这其实是十分耗性能的。
pcntl_signal的实现原理是,触发信号后先将信号加入一个队列中。然后在PHP的ticks回调函数中不断检查是否有信号,如果有信号就执行PHP中指定的回调函数,如果没有则跳出函数。
在PHP5.3之后,有了pcntlsignaldispatch函数。这个时候将不在需要declare,只需要在循环中增加该函数,就可以调用信号通过了:
大家都知道PHP的ticks=1表示每执行1行PHP代码就回调此函数。实际上大部分时间都没有信号产生,但ticks的函数一直会执行。如果一个服务器程序1秒中接收1000次请求,平均每个请求要执行1000行PHP代码。那么PHP的pcntlsignal,就带来了额外的 1000 * 1000,也就是100万次空的函数调用。这样会浪费大量的CPU资源。比较好的做法是去掉ticks,转而使用pcntlsignal_dispatch,在代码循环中自行处理信号。
pcntlsignaldispatch 函数的实现:
但是上边这种,也有个恶心的地方就是,它得放在死循环中。PHP7.1之后出来了一个完成异步的信号接收并处理的函数:
pcntlasyncsignals
通过pcntlasyncsignals方法,就不用再写死循环了。
监听信号的包:
版权声明: 本文为 InfoQ 作者【书旅】的原创文章。
原文链接:【http://xie.infoq.cn/article/a8298b8c72d2676788c0b730a】。文章转载请联系作者。
评论