PHP电商转化断点诊断:从点击到下单的网关级剖析
|
线上报警那一刻,我正把冷掉的挂耳咖啡往杯里倒第三泡——水温早过了萃取线,苦得像在嚼运维上月写的回滚文档。手机亮了一下,是前端老张发来的截图:「转化漏斗从商品页跳转到下单页,断在中间了,但所有下游服务返回200」。我第一反应是去翻网关日志,结果发现那条请求压根没进网关——它被 nginx 层拦在 upstream timeout 之前,连 traceid 都没生成。你猜怎么着?是 PHP-FPM 的 `request_terminate_timeout` 在背后掐着脖子,而我们网关配置里还写着「PHP 后端超时兜底 15s」,纯属自我安慰。 说白了,去年11月份那会儿,整个转化链路的「断点」根本不在业务层,也不在网关路由或鉴权环节,就在 PHP 进程池和网关之间那层薄得像卫生纸的 socket 缓冲区里。我们以为网关能 catch 住所有异常,结果它连异常的脸都没见着——PHP worker 自己凉了,只留下半截 HTTP 响应头,后面 body 全喂了 TCP 重传机制。运维小王当时重启了三回 FPM 池,重启完跑了一小时就又卡住,我问他监控里看什么指标,他答:「CPU?内存?都正常。我就盯着 slowlog,但 slowlog 里压根没这条请求记录。」 窗外天黑了,工单系统又弹了条消息:「【P0】大促期间加购转下单率跌 12%(相对上周)」。这数字我原以为是前端埋点错位导致的假阳性,结果抓包一比对,发现真实请求流量有接近一半卡在「点击跳转」之后、「下单页首屏渲染完成」之前——不是接口没响应,是响应发了一半,浏览器等不及就发了第二个请求,然后两个请求挤在同一 session 上,触发了 PHP 的 session_start() 锁争抢。我查了 session.save_handler,是 files,没错;查了 session.save_path,是 /tmp,也没错;可没人想到 /tmp 底下那个叫 sess_ 开头的文件,读写锁粒度大到离谱,而且 PHP-FPM 主进程根本不会释放这个锁,除非 worker 进程 exit 或 recycle。所以,当某个慢查询拖住一个 worker,它拿着 session 文件锁不撒手,后面十个用户全卡在「准备渲染下单页」这个阶段,页面白屏,F5 一下,锁换了人拿,再 F5,又卡。这不是并发问题,这是串行化地狱。 谁的键盘声很响,是隔壁组在敲 CI 脚本,啪嗒啪嗒,跟心跳似的。我顺手 grep 了下我们网关代码里所有带「php」字样的注释,有十七处写着「兼容 legacy PHP 接口」,其中九处连对应的 PHP 版本号都没标。最绝的是 vendor 目录下那个 utils.php,文件头注释写着「v2.1(2016年修订)」,但 git blame 显示最后修改是前年五一,改的人留了句 comment:「修复 header 写入顺序 bug —— 但这不是 header bug,是 session 问题」,然后就没下文了。我原以为这段逻辑早被废弃了,结果上周灰度切流时发现,下单页的 coupon 校验路径居然还在走这个函数……它里面调用了 setcookie() 前没 check headers_sent(),直接崩出 warning,而我们的网关对 warning 级别日志默认静默丢弃——因为「PHP warning 不影响 HTTP status」。呵,不影响 status,但影响 response body 里那段 JS 初始化代码,JS 少执行一行,埋点就不打,转化率就掉,数据就失真。优胜劣汰?对,淘汰的就是这种「看着正常、跑着残废、查着懵逼」的缝合逻辑。
文章配图,仅供参考 产品小陈昨天拉会吵了半小时,说「你们网关能不能加个开关,让前端自己控制要不要校验优惠券」,我说「你让 PHP 接口先支持幂等重试再说」,她愣了三秒回:「PHP 不能重试?」我差点把咖啡泼在键盘上。其实 PHP 当然能重试,但它重试的代价是 session lock 加持下的 N 倍延时,以及下游 MySQL 里一堆脏状态的订单草稿。我们不敢重试,不是技术不行,是整条链路上二十多个服务,有八个没实现补偿,剩下六个的补偿脚本还躺在测试环境没过冒烟……算了,不说这个。前两天听说旧同事老李跳去搞 Rust 微服务了,朋友圈晒了张图:「终于不用在 header 发送后还硬塞 setcookie」。我没点赞,但截图保存了。其实现在回头看,诊断 PHP 电商转化断点这事本身不难,难的是要同时理解网关的请求生命周期、PHP 运行时的底层行为、HTTP 协议栈在不同 buffer 阶段的表现,还有——最关键的是——谁他妈还在用 session files。但我们没资格鄙视它,因为就是靠这套玩意儿撑住了去年双十二,虽然峰值期间下单页加载平均花了 3.7 秒,但没挂。至于今年要不要换 redis-session?排期表上写着 Q2 中,但我估计会拖到秋招新来的同学接手,毕竟老张下周就要去带新人做前端 SDK 改造,小王刚考完 K8s 认证,打算申请调岗去 infra 组。我呢?刚收到 HR 邮件,说年度调薪流程启动了,附件里有个「技术影响力自评表」,要求填三项「关键产出」。我在第一栏写了:「把 PHP session 文件锁问题从‘玄学’降级为‘已知但暂不修’」。第二栏空着。第三栏犹豫了半天,填了:「继续盯着 nginx error_log 里那行 ‘upstream timed out’,看它哪天开始撒谎」。 下一步?今晚先把网关里所有 php://input 的 raw body 读取逻辑拎出来,加个 try-catch+traceid 打点——不是为了修复,是为了确认它到底有没有机会进到 catch 里。如果进不去,说明问题还在更前面,比如 SSL handshake 后的 TCP window scaling 失配。但如果进去了……那我们可能得重新审一遍那十七处「兼容 legacy PHP 接口」的注释,挨个点开,看看哪一行注释后面,藏着一句没写的「此处已失效,但不敢删」。我现在不确定。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

