现在有一种情形,容器中有一个常驻进程,该常驻进程的任务是不断的消费行列步队里的。
假设现在要上线,须要关杀掉容器,Docker给容器里跑的常驻进程发送一个旗子暗记,见告它我10s后会将你关闭,假设现在已经由了9秒,常驻进程刚从行列步队中取出一条,1s内还没将后续逻辑实行完,进程就已经被杀了,此时这条就丢失了,且可能会产生脏数据

上边便是这次任务的背景,须要通过监听旗子暗记来决定后续如何操作。
对付上边这种情形,当常驻进程收到Docker发送的关闭旗子暗记时,将该进程壅塞即可,一贯sleep,直到杀掉容器。
OK,清楚背景之后,下边就先容一下PHP中的旗子暗记(后边会再整理一篇这个包如何写,并将包发布到https://packagist.org/,供须要的小伙伴利用)

一、在Linux操作系统中有哪些旗子暗记1、大略先容旗子暗记

旗子暗记是事宜发生时对进程的关照机制,有时又称为软件中断。
一个进程可以向另一个进程发送旗子暗记,比如子进程结束时都会向父进程发送一个SIGCHLD(17号旗子暗记)来关照父进程,以是有时旗子暗记也被当作一种进程间通信的机制。

php的handler一文吃透 PHP 过程旌旗灯号处置 React

在linux系统下,常日我们利用 kill -9 XXPID来结束一个进程,实在这个命令的本色便是向某进程发送SIGKILL(9号旗子暗记),对付在前台进程我们常日用Ctrl+c快捷键来结束运行,该快捷键的本色是向当提高程发送SIGINT(2号旗子暗记),而进程收到该旗子暗记的默认行为是结束运行

2、常用旗子暗记

下边这些旗子暗记,可以利用kill -l命令进行查看

下边先容几个比较主要且常用的旗子暗记:

旗子暗记名 旗子暗记值 旗子暗记类型 旗子暗记描述

SIGHUP 1 终止进程(终端线路挂断) 本旗子暗记在用户终端连接(正常或非正常、结束时发出, 常日是在终真个掌握进程结束时, 关照同一session内的各个作业, 这时它们与掌握终端不再关联

SIGQUIT 2 终止进程(中断进程) 程序终止(interrupt、旗子暗记, 在用户键入INTR字符(常日是Ctrl-C、时发出

SIGQUIT 3 建立CORE文件终止进程,并且天生CORE文件 进程,并且天生CORE文件 SIGQUIT 和SIGINT类似, 但由QUIT字符(常日是Ctrl-、来掌握. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序缺点信 号

SIGFPE 8 建立CORE文件(浮点非常) SIGFPE 在发生致命的算术运算缺点时发出. 不仅包括浮点运算缺点, 还包括溢 出及除数为0等其它所有的算术的缺点

SIGKILL 9 终止进程(杀去世进程) SIGKILL 用来立即结束程序的运行. 本旗子暗记不能被壅塞, 处理和忽略

SIGSEGV 11 SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据

SIGALRM 14 终止进程(计时器到时) SIGALRM 时钟定时旗子暗记, 打算的是实际的韶光或时钟韶光. alarm函数利用该旗子暗记

SIGTERM 15 终止进程(软件终止旗子暗记) SIGTERM 程序结束(terminate、旗子暗记, 与SIGKILL不同的是该旗子暗记可以被壅塞和处理. 常日用来哀求程序自己正常退出. shell命令kill缺省产生这个旗子暗记

SIGCHLD 17 忽略旗子暗记(当子进程停滞或退出时关照父进程) SIGCHLD 子进程结束时, 父进程会收到这个旗子暗记

SIGVTALRM 26 终止进程(虚拟计时器到时) SIGVTALRM 虚拟时钟旗子暗记. 类似于SIGALRM, 但是打算的是该进程占用的CPU韶光

SIGIO 29 忽略旗子暗记(描述符上可以进行I/O) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作

二、PHP中处理旗子暗记干系函数

PHP的pcntl扩展以及posix扩展为我们供应了多少操作旗子暗记的方法(若想利用这些函数,须要先安装这几个扩展)

下边详细先容几个我在本次任务中用到的方法:

declare

declare构造用来设定一段代码的实行指令。
declare的语法和其它流程掌握构造相似

declare(directive)statement

directive部分许可设定declare代码段的行为。
目前只认识两个指令:ticks和encoding。
declare代码段中的 statement部分将被实行——若何实行以及实行中有什么副浸染涌现取决于directive中设定的指令

Ticks

Tick(时钟周期)是一个在declare代码段中阐明器每实行N条可计时的低级语句就会发生的事宜N的值是在declare 中的directive部分用ticks=N来指定的。
不是所有语句都可计时。
常日条件表达式和参数表达式都不可计时。
在每个tick中涌现的事宜是由register_tick_function()来指定的,把稳每个 tick 中可以涌现多个事宜 更详细的内容,可查看官方文档:https://www.php.net/manual/zh/control-structures.declare.php

<?phpdeclare(ticks=1);//每实行一条时,触发register_tick_function()注册的函数$a=1;//在注册之前,不算functiontest(){//定义一个函数echo"实行\n";}register_tick_function('test');//该条注册函数会被当成低级语句被实行for($i=0;$i<=2;$i++){//for算一条低级语句$i=$i;//赋值算一条}输出:六个“实行”pcntl_signal

pcntl_signal,安装一个旗子暗记处理器

pcntl_signal(int$signo,callback$handler[,bool$restart_syscalls=true]):bool

函数pcntl_signal()为signo指定的旗子暗记安装一个新的旗子暗记处理器

declare(ticks=1);pcntl_signal(SIGINT,function(){echo"你按了Ctrl+C".PHP_EOL;});while(1){sleep(1);//去世循环运行低级语句}输出:当按Ctrl+C之后,会输出“你按了Ctrl+C”posix_kill

posix_kill,向进程发送一个旗子暗记

posix_kill(int$pid,int$sig):bool

第一个参数为进程ID,第二个参数为你要发送的旗子暗记

a.php<?phpdeclare(ticks=1);echogetmypid();//获取当提高程idpcntl_signal(SIGINT,function(){echo"你给我发了SIGINT旗子暗记";});while(1){sleep(1);}b.php<?phpposix_kill(实行1.php时输出的进程id,SIGINT);pcntl_signal_dispatch

pcntl_signal_dispatch,调用等待旗子暗记的处理器

pcntl_signal_dispatch(void):bool

函数pcntl_signal_dispatch()调用每个等待旗子暗记通过pcntl_signal()安装的处理器

<?phpecho"安装旗子暗记处理器...\n";pcntl_signal(SIGHUP,function($signo){echo"旗子暗记处理器被调用\n";});echo"为自己天生SIGHUP旗子暗记...\n";posix_kill(posix_getpid(),SIGHUP);echo"分发...\n";pcntl_signal_dispatch();echo"完成\n";?>输出:安装旗子暗记处理器...为自己天生SIGHUP旗子暗记...分发...旗子暗记处理器被调用完成pcntl_async_signals()

异步旗子暗记处理,用于启用无需 ticks (这会带来很多额外的开销)的异步旗子暗记处理。
(PHP>=7.1)

<?phppcntl_async_signals(true);//turnonasyncsignalspcntl_signal(SIGHUP,function($sig){echo"SIGHUP\n";});posix_kill(posix_getpid(),SIGHUP);输出:SIGHUP三、PHP中处理旗子暗记量的办法

前边我们知道我们可以通过declare(ticks=1)和pcntl_signal组合的办法监听旗子暗记,即每一条PHP低级语句,就会检讨一次当提高程是否有未处理的旗子暗记,这实在是十分耗性能的。

pcntl_signal的实现事理是,触发旗子暗记后先将旗子暗记加入一个行列步队中。
然后在PHP的ticks回调函数中不断检讨是否有旗子暗记,如果有旗子暗记就实行PHP中指定的回调函数,如果没有则跳出函数。

PHP_MINIT_FUNCTION(pcntl){php_register_signal_constants(INIT_FUNC_ARGS_PASSTHRU);php_pcntl_register_errno_constants(INIT_FUNC_ARGS_PASSTHRU);php_add_tick_function(pcntl_signal_dispatchTSRMLS_CC);returnSUCCESS;}

在PHP5.3之后,有了pcntl_signal_dispatch函数。
这个时候将不在须要declare,只须要在循环中增加该函数,就可以调用旗子暗记通过了:

<?phpechogetmypid();//获取当提高程idpcntl_signal(SIGUSR1,function(){echo"触发旗子暗记用户自定义旗子暗记1";});while(1){pcntl_signal_dispatch();sleep(1);//去世循环运行低级语句}

大家都知道PHP的ticks=1表示每实行1行PHP代码就回调此函数。
实际上大部分韶光都没有旗子暗记产生,但ticks的函数一贯会实行。
如果一个做事器程序1秒中吸收1000次要求,均匀每个要求要实行1000行PHP代码。
那么PHP的pcntl_signal,就带来了额外的 1000 1000,也便是100万次空的函数调用。
这样会摧残浪费蹂躏大量的CPU资源。
比较好的做法是去掉ticks,转而利用pcntl_signal_dispatch,在代码循环中自行处理旗子暗记。
pcntl_signal_dispatch 函数的实现:

voidpcntl_signal_dispatch(){//....这里略去一部分代码,queue即是旗子暗记行列步队while(queue){if((handle=zend_hash_index_find(&PCNTL_G(php_signal_table),queue->signo))!=NULL){ZVAL_NULL(&retval);ZVAL_LONG(¶m,queue->signo);/Callphpsignalhandler-Notethatwedonotreporterrors,andweignorethereturnvalue//FIXME:thisisprobablybrokenwhenmultiplesignalsarehandledinthiswhileloop(retval)/call_user_function(EG(function_table),NULL,handle,&retval,1,¶mTSRMLS_CC);zval_ptr_dtor(¶m);zval_ptr_dtor(&retval);}next=queue->next;queue->next=PCNTL_G(spares);PCNTL_G(spares)=queue;queue=next;}}

但是上边这种,也有个恶心的地方便是,它得放在去世循环中。
PHP7.1之后出来了一个完成异步的旗子暗记吸收并处理的函数: pcntl_async_signals

<?php//a.phpechogetmypid();pcntl_async_signals(true);//开启异步监听旗子暗记pcntl_signal(SIGUSR1,function(){echo"触发旗子暗记";posix_kill(getmypid(),SIGSTOP);});posix_kill(getmypid(),SIGSTOP);//给进程发送停息旗子暗记//b.phpposix_kill(文件1进程,SIGCONT);//给进程发送连续旗子暗记posix_kill(文件1进程,SIGUSR1);//给进程发送user1旗子暗记

通过pcntl_async_signals方法,就不用再写去世循环了。

监听旗子暗记的包:

https://github.com/Rain-Life/monitorSignal