PHP调试总卡壳?云成本工程师的3步精准定位法
|
去年10月,上海机房那台ECS的CPU突然在凌晨三点十七分开始间歇性吃满——不是持续打满,是每四十一秒来一波尖峰,像有人在定时按Ctrl+C又松手。我盯着CloudWatch里那段锯齿,手已经摸到键盘想改PHP-FPM的pm.max_children了,结果运维老张端着保温杯路过,说:“别动,先看日志。”他刚说完,保洁阿姨推着水桶从机柜过道穿过去,水桶沿儿磕在冷通道铝板上,“当啷”一声震得我耳机里日志滚动都卡了半拍。 PHP调试总卡壳?云成本工程师的3步精准定位法。这话不是我编的,是那天早上八点二十三分,我在内网复盘通报里写的标题。但你真信这标题能上热搜?呵,我自己都不信。它站得住的地方就一个:优胜劣汰。不是技术优劣,是——谁还能忍着查三小时slowlog、结果发现是开发同事把date_default_timezone_set()写进了foreach里?这种人,活该被甩锅,也活该被淘汰。我敢打包票,那个foreach循环里调了七次date_default_timezone_set(),每次耗时十八秒——不是毫秒,是秒。你去测,用microtime(true)包着测,我赌一包卫龙大面筋。 第一步,不看代码,先扒资源拓扑。你打开控制台第一眼盯的是错误日志还是ECS监控图?错。要看SLA链路里那一截最短的毛细血管:比如某次请求链路里,MySQL慢查询才0.8秒,但PHP层Wait Time却标红2.3秒——这2.3秒里没IO、没锁、没RPC超时,纯等待。那天我就盯住这个2.3,顺藤摸到php-fpm的access.log,发现同一分钟有三百七十二个请求在“Access denied”后面跟着“Unable to connect to upstream”,而上游明明没挂。后来才发现是nginx upstream配置里少了个max_fails=0,但故障期间根本没人改过nginx配置……等等,不对——我想起来了,是前一周测试同学手动curl了二十多次/healthz接口,触发了默认的max_fails=1,导致节点被踢出。我原以为是PHP脚本阻塞,结果是健康检查自己把自己放倒了。你猜怎么着?运维老张当时正接一个电话,左手比划“马上到”,右手已经在SSH里敲rm -f /var/run/php/php7.4-fpm.sock,我说别删!他没听见,删了,接着又restarted——好家伙,fpm重启花了四秒三,期间所有worker全跪。这四秒三,后来成了我们团队周会固定黑话:“张氏四秒三”。 第二步,不做假设,只切现场快照。不用xdebug,不上blackfire,更不翻Git blame。直接ssh进实例,top -H;然后挑一个高CPU的php进程,用strace -p {pid} -T -e trace=all -s 128。你以为会看到epoll_wait反复返回-1?不。那天我看到的是三个syscall来回跳:getcwd(0.000123s)、stat("/proc/self/cwd", ...)(0.000092s)、openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC)(0.000056s)。就这么三行,重复了六百多遍。为什么?因为某个require_once()路径写成../../config/../env/../config/env.php,PHP每次解析都要层层cd上去再下来。而/etc/localtime是软链接,每次stat完还得readlink,readlink完还要open……整套动作在PHP用户态堆叠里反复打转。你问我为啥不早发现?因为我桌上还剩半盒没拆封的酸辣粉,汤都凉透了,塑料盖子上凝了层油膜,我盯着那层油膜看了四十秒,才想起来去strace。现在想想,那油膜纹路还挺像火焰图的。
文章配图,仅供参考 第三步?没有第三步。所谓第三步是唬人的。真正管用的永远是第四步:拉人进飞书群,@所有人,“请各位今晚十点前确认自己线上环境的date.timezone配置是否统一”。发完我就关电脑走了。第二天晨会,DBA小陈低头抠指甲,开发组长老赵一边啃粢饭糕一边说“这个我们组肯定没问题”,结果下午三点查出来他们用了三个不同timezone:Asia/Shanghai、PRC、+08:00——全是合法值,全在php.ini里硬编码,全跑在同一台机器上。谁干的?不知道。但那个写+08:00的人,上周刚提了离职。这事最后没追究,因为——对,因为财务部邮件抄送过来说上季度云支出超了预算的17.8%,领导要求“立刻优化”。所以你看,PHP调试卡壳?最后卡在哪儿?卡在老板邮箱里那句“请于本周五下班前提交降本方案”。 说到这儿,其实我想起另一件事。上周四,隔壁组部署了个新服务,用了Laravel Octane,Swoole启动失败三次后自动fallback到FPM,但监控没报警,告警规则写的是“process_num < 1”,可Octane fallback后fpm worker还在跑,只是QPS掉到三百以下……算了,这和PHP调试没关系,不说了。我刚瞥见微信消息弹窗,是测试同学发来的截图:“王工,你们环境里phpinfo()页面能打开,但show_source=On参数好像不生效?”我没回。这种事,三年前就该死透了。 你问我现在用什么办法?简单。每周二下午三点,我登录阿里云ASCM控制台,在弹性计算 > 实例 > 筛选标签“php_version=7.4”,点批量停止。停之前,把所有正在执行的strace进程都kill -9掉——就为防止有人误操作把trace文件写满磁盘。至于调试?我现在让实习生干。他年轻,手指快,愿意在凌晨两点逐行比对php --ini输出和docker build日志的差异。我不拦他。让他比。反正,他比完我也不会看。我只看他有没有在git commit message里写清楚哪一行加了@var string $timestamp——写了,就留;没写,下周换人。 最后说一句实在的:这套方法,你拿去用,可能三天就崩。因为今年五月,阿里云ECS的底层宿主机Kernel打了补丁,getcwd syscall行为变了,有些路径解析不再触发重复stat。所以我那套strace打法,现在只在华北2可用。华东1?不行。你说要不要适配?我懒得动。反正下周我们要切到Serverless函数计算,PHP运行时版本是8.3——连php.ini都没地方改。到时候,谁还debug?等日志炸了,直接删函数、重建。够快。够便宜。够干净。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |



