PHP电商下单链路断点诊断:14年接口测试实战
|
上周五下午三点十七,王磊把订单号贴我屏幕上时,我盯着那个“pending_payment”卡了三秒——这状态在支付回调里根本没定义过。他手还在抖,我 keyboard上那个N键按下去不回弹,敲了个“no”变成“nooooooooo”,他自己先笑了。 白板上的数字擦了一半,307后面拖着半截“/500”,估计是上次压测的残骸。会议室又被占了,门缝底下漏出PPT翻页笔的红点,在我们脚边扫来扫去。我原以为又是支付网关超时,结果扒日志发现是库存扣减后,PHP那层用microtime()拼了个时间戳当幂等键,而集群两台机器差了12毫秒——同一单发两次,第二次直接判重复丢弃。开发说“不可能差这么多”,我把dmesg截图甩过去,他盯了十五秒,把咖啡泼自己裤子上了。
文章配图,仅供参考 谁桌上摆着三台手机,红米、iPhone、还有台锤子,充着电但都黑着屏。测试机的充电线不够长,每次跑用例得把椅子往插座方向拖三十公分。用例管理工具一直在转圈,圈了整整四十分钟,我切出去改了三次邮箱配置才回来——它好了。王磊说“你看,系统自己恢复了”,我说你管这叫恢复?这是缓存刷掉了而已。 优胜劣汰?对。不是人淘汰人,是链路里扛不住压的模块自动被甩出去。去年那个致命问题——下单成功但没生成物流单号,就是优惠券核销和运单创建两个事务在Redis pipeline里抢同一个连接池,PHP-fpm进程一重启,中间状态就丢了。不是逻辑写错,是连接复用策略和重试机制没对齐。李薇当场在GitLab提了个RFC,标题写着“别让订单在中间态呼吸”,三天没人review,最后是运维小张在值班群里顺手merge的。你猜为啥?因为他前两天刚在生产库误删过一张表,心虚。 外卖凉透了,塑料盒底结了层油膜,筷子头还沾着一点豆瓣酱。我扒拉两口就不吃了。这事儿要真说透,就得讲清楚为什么“订单已创建”和“支付状态同步”必须解耦到三个不同服务里——可我不想讲。讲了也没用。上次讲完,开发点头如捣蒜,第二天晨会说“先上个简易版,灰度看效果”,结果上线三小时,14%用户收到两条支付成功短信,客服电话被打爆,但没人提单,因为日志里全是“success:true”。 我不信日志,信数据库binlog里的insert timestamp和update timestamp差值。查了十七单,有九单update比insert早89毫秒——PHP那堆date_default_timezone_set('Asia/Shanghai')全在Docker容器里执行,而MySQL用的是UTC。你跟开发讲这个,他掏出手机查“PHP date timezone docker”,搜出来第一条就是Stack Overflow里有人写了四年前的答案:“改php.ini,加docker restart”。他抄了,重启完,连不上Redis。我懒得管,去工位抽屉摸薄荷糖,摸到半块化掉的。键盘上那个N键还是不回弹。 跟开发争执那次,其实不是为bug,是为要不要给“库存锁失败”返回个code 429。他说“429是限流,咱们是业务并发冲突”,我说“那用户看到500总比看到‘操作失败’强”。最后折中:前端统一拦截500并展示“稍等重试”,而PHP那边悄悄把429转成500……漏测的案例?对,去年双十一前夜,测试环境所有订单都没走短信通道,因为短信服务地址配在.env.local,而发布脚本只加载.env.production。没人碰local文件——除了那个新来的实习生,他本地调试改完忘了撤,提了PR,CI通过,上线。线上零投诉,因为压根没触发短信逻辑。我们所有人,包括产品,都以为短信通着呢。 我自己那份没人看的用例,第142条写着“跨时区下单并发场景下,检查订单创建与支付回调的时间戳一致性”,但没写校验方式。当时觉得太绕,写了也没人读。现在想想,绕是对的。不绕的,早被砍了。 记不清是哪个迭代了,大概那个数:连续三周,每天晚上十一点多,陈默还在工位调curl命令。他不跑UI,专盯HTTP Header里的X-Request-ID,说这个ID得穿透全部中间件。有次他硬塞给我一个日志片段,里面ID串了七次,第七次结尾多冒了个空格——他就靠这个空格定位到Kafka消费者组里的一个无脑trim()。我没敢接话,怕他说“你也该试试”,我连他用什么工具截的日志都不知道。用例管理工具又开始转圈了。 所以我现在写断点诊断文档,不写结论,就写三样东西:哪里断了(精确到中间件节点),断之前最后一行有效日志的trace_id,以及,那一单里哪两个字段的值组合,能稳定复现。别的?爱看不看。要真想懂,自己去跑一次全链路mock,别拿本地PHP-fpm进程凑数。你跑不出来,我就当你默认接受现状。 王磊刚微信问我,“灰度放量能不能再拖一天?”——我不回。等他问第三遍,我再贴一段日志:payment_callback.php第87行,$ret = $this->retry($payload, 3);但retry函数内部没判断$payload是否已被消费过。就这么一行,够他重写半小时。至于要不要补日志、加埋点、改架构……随他。我只管标出断点位置。剩下的,优胜劣汰。 用例管理工具停转了。这次没转,直接弹了个框:“Connection timeout: API gateway unreachable”。挺好。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


Linux下PHP开发环境搭建实战指南
PHP服务器安全审计:90%高危漏洞藏于配置末行
PHP防SQL注入:三层硬核防御体系
PHP工程师必避的5大服务器选型陷阱与调优实战
PHP工程师别只写代码:外链才是权重跃升的隐藏杠杆
PHP工程师必备:3个数据建模技巧提升决策效率70%
PHP建站避坑:90%开发者忽略的框架选型真相

