记一次php高负载问题排查经历
第一步先使用strace查看系统调用情况:
strace -tt -T -c -p 31420
strace -c发现,系统时间主要耗费在rt_sigprocmask上,该函数是系统的信号屏蔽函数,一般使用在信号处理程序中,对信号进行屏蔽,保证信号处理程序不被打断。rt_sigprocmask为linux的系统调用函数,不对外直接调用,而使用glibc对其的封装:sigprocmask.
进一步starce调用实际情况:
rt_singprocmask调用有一个特征:设置屏蔽字后紧接着恢复屏蔽字,可以联想到这有可能php的信号处理函数中调用的。
在php源码中搜索sigprocmask,发现php源码中调用singprocmask的地方主要在pnctl扩展中,在deamon程序使用了pnctl扩展创建和管理子进程。通过先阻塞信号后恢复信号的特征,定位到php的信号处理函数:pcntl_signal_dispatch()中。
接着通过加日志验证我们的想法,确认确实在频繁调用该函数。由此引出了对php信号处理机制的分析。
限于篇幅,具体分析过程省略,这里给出分析的结果:
- php的tick机制:
phpmanul对tick的解释是:
A tick is an event that occurs for every N low-level tickable statements executed by the parser within the declare block. The value for N is specified using ticks=N within the declare block's directive section.
Not all statements are tickable. Typically, condition expressions and argument expressions are not tickable.
The event(s) that occur on each tick are specified using the register_tick_function(). See the example below for more details. Note that more than one event can occur for each tick.
大概意思是:tick和register_tick_function配合,每执行tick=n的n个底层指令,就触发执行register_tick_function注册的事件。
2. php对信号的处理:
Php对进程和信号的管理控制通过pcntl扩展实现,pcntl使用了tick机制作为signal的callback机制,你可以在php中允许callback的地方使用declare允许callback发生。
通过对pcntl源码分析,pcntl的信号处理机制是这样的:
1),信号注册
2),信号处理:
分析到这里,可以看到:在php中,根据declare声明的tick值来定期调用,当tick值配置较低时(本程序中定义ticks=1),会大量触发信号处理程序,该程序中调用rt_sigprocmask导致浪费大量的cpu,机器负载变高。
那么php为什么要采用这种信号处理机制呢?
查看在php manual时,发现这样的介绍:
PCNTL now uses ticks as the signal handle callback mechanism, which is much faster than the previous mechanism. This change follows the same semantics as using "user ticks". You use the declare() statement to specify the locations in your program where callbacks are allowed to occur. This allows you to minimize the overhead of handling asynchronous events. In the past, compiling PHP with pcntl enabled would always incur this overhead, whether or not your script actually used pcntl.
重点关注:You use the declare() statement to specify the locations in your program where callbacks are allowed to occur. 意思是可以指定需要捕获信号的代码区域,区域外则不进行信号处理。
由上可以总结出修改问题的办法:
1),通过declare制定应用程序捕捉信号的区域,
2),对于不需要进行信号处理的程序,不声明declear(ticks=n),由此不会触发调用信号处理程序;需要处理的程可以手动调用信号处理程序pcntl_signal_dispatch代替php的定时检测。
经过验证,采用以上任何一种办法都可以解决问题。机器负载降到正常值。
总结:
1),php采用类似linux内核中断处理机制(硬中断+软终端)机制处理信号,需要注意检测信号的频次,避免cpu性能浪费。
2),因为在cgi中不建议使用pcntl扩展管理创建进程,该机制不会对webserver的cgi产生影响