PHP实现通过strace定位故障原因的方法
本文实例讲述了PHP实现通过strace定位故障原因的方法。分享给大家供大家参考,具体如下:
俗话说:不怕贼偷,就怕贼惦记着。在面对故障的时候,我也有类似的感觉:不怕出故障,就怕你不知道故障的原因,故障却隔三差五的找上门来。
十一长假还没结束,服务器却频现高负载,Nginx出现错误日志:
connect()failed(110:Connectiontimedout)whileconnectingtoupstream
connect()failed(111:Connectionrefused)whileconnectingtoupstream
看上去是Upstream出了问题,在本例中Upstream就是PHP(版本:5.2.5)。可惜监控不完善,我搞不清楚到底是哪出了问题,无奈之下只好不断重启PHP来缓解故障。
如果每次都手动重启服务无疑是个苦差事,幸运的是可以通过CRON设置每分钟执行:
#/bin/bash LOAD=$(awk'{print$1}'/proc/loadavg) if[$(echo"$LOAD>100"|bc)=1];then /etc/init.d/php-fpmrestart fi
可惜这只是一个权宜之计,要想彻底解决就必须找出故障的真正原因是什么。
闲言碎语不要讲,轮到Strace出场了,统计一下各个系统调用的耗时情况:
shell>strace-c-p$(pgrep-nphp-cgi) %timesecondsusecs/callcallserrorssyscall -------------------------------------------------------------- 30.530.023554132179brk 14.710.01135014081mlock 12.700.0097981565816recvfrom 8.960.0069107927read 6.610.00509743119accept 5.570.0042944977poll 3.130.0024157359write 2.820.0021777311sendto 2.640.002033212011stat 2.270.00175012312gettimeofday 2.110.00162611428rt_sigaction 1.550.0011992730fstat 1.290.00099810100100connect 1.030.0007924178shutdown 1.000.0007732492open 0.930.0007201711close 0.490.0003812238chdir 0.350.000271387select 0.290.0002241357setitimer 0.210.000159281munlock 0.170.000133288getsockopt 0.140.0001101149lseek 0.140.0001061121mmap 0.110.0000861121munmap 0.090.0000720238rt_sigprocmask 0.080.000063417lstat 0.070.0000540313uname 0.000.0000000151access 0.000.0000000100socket 0.000.0000000101setsockopt 0.000.0000000277fcntl -------------------------------------------------------------- 100.000.07714513066118total
看上去「brk」非常可疑,它竟然耗费了三成的时间,保险起见,单独确认一下:
shell>strace-T-ebrk-p$(pgrep-nphp-cgi) brk(0x1f18000)=0x1f18000<0.024025> brk(0x1f58000)=0x1f58000<0.015503> brk(0x1f98000)=0x1f98000<0.013037> brk(0x1fd8000)=0x1fd8000<0.000056> brk(0x2018000)=0x2018000<0.012635>
说明:在Strace中和操作花费时间相关的选项有两个,分别是「-r」和「-T」,它们的差别是「-r」表示相对时间,而「-T」表示绝对时间。简单统计可以用「-r」,但是需要注意的是在多任务背景下,CPU随时可能会被切换出去做别的事情,所以相对时间不一定准确,此时最好使用「-T」,在行尾可以看到操作时间,可以发现确实很慢。
在继续定位故障原因前,我们先通过「manbrk」来查询一下它的含义:
brk()setstheendofthedatasegmenttothevaluespecifiedbyend_data_segment,whenthatvalueisreasonable,thesystemdoeshaveenoughmemoryandtheprocessdoesnotexceeditsmaxdatasize(seesetrlimit(2)).
简单点说就是内存不够用时通过它来申请新内存(datasegment),可是为什么呢?
shell>strace-T-p$(pgrep-nphp-cgi)2>&1|grep-B10brk stat("/path/to/script.php",{...})=0<0.000064> brk(0x1d9a000)=0x1d9a000<0.000067> brk(0x1dda000)=0x1dda000<0.001134> brk(0x1e1a000)=0x1e1a000<0.000065> brk(0x1e5a000)=0x1e5a000<0.012396> brk(0x1e9a000)=0x1e9a000<0.000092>
通过「grep」我们很方便就能获取相关的上下文,反复运行几次,发现每当请求某些PHP脚本时,就会出现若干条耗时的「brk」,而且这些PHP脚本有一个共同的特点,就是非常大,甚至有几百K,为何会出现这么大的PHP脚本?实际上是程序员为了避免数据库操作,把非常庞大的数组变量通过「var_export」持久化到PHP文件中,然后在程序中通过「include」来获取相应的变量,因为变量太大,所以PHP不得不频繁执行「brk」,不幸的是在本例的环境中,此操作比较慢,从而导致处理请求的时间过长,加之PHP进程数有限,于是乎在Nginx上造成请求拥堵,最终导致高负载故障。
下面需要验证一下推断似乎否正确,首先查询一下有哪些地方涉及问题脚本:
shell>find/path-name"*.php"|xargsgrep"script.php"
直接把它们都禁用了,看看服务器是否能缓过来,或许大家觉得这太鲁蒙了,但是特殊情况必须做出特殊的决定,不能像个娘们儿似的优柔寡断,没过多久,服务器负载恢复正常,接着再统计一下系统调用的耗时:
shell>strace-c-p$(pgrep-nphp-cgi) %timesecondsusecs/callcallserrorssyscall -------------------------------------------------------------- 24.500.001521111382recvfrom 16.110.0010003330accept 7.860.000488859sendto 7.350.0004561360rt_sigaction 6.730.0004182198poll 5.720.0003551285stat 4.540.0002820573gettimeofday 4.410.000274742shutdown 4.400.0002732137open 3.720.0002311197fstat 2.930.0001821187close 2.560.000159290setitimer 2.130.0001321244read 1.710.000106430munmap 1.160.000072160chdir 1.130.000070418setsockopt 1.050.0000651100write 1.050.000065164lseek 0.950.000059175uname 0.000.000000030mmap 0.000.000000060rt_sigprocmask 0.000.000000032access 0.000.00000009select 0.000.000000020socket 0.000.00000002020connect 0.000.000000018getsockopt 0.000.000000054fcntl 0.000.00000009mlock 0.000.00000009munlock -------------------------------------------------------------- 100.000.006208311924total
显而易见,「brk」已经不见了,取而代之的是「recvfrom」和「accept」,不过这些操作本来就是很耗时的,所以可以定位「brk」就是故障的原因。
…
拥抱故障,每一次故障都是历练。正所谓:天将降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为,所以动心忍性,增益其所不能。
更多关于PHP相关内容感兴趣的读者可查看本站专题:《PHP错误与异常处理方法总结》、《php字符串(string)用法总结》、《PHP数组(Array)操作技巧大全》、《PHP运算与运算符用法总结》、《PHP网络编程技巧总结》、《PHP基本语法入门教程》及《php面向对象程序设计入门教程》
希望本文所述对大家PHP程序设计有所帮助。