/ 闭门造轮子 / PHP cURL 超时优化案例

PHP cURL 超时优化案例

2018-07-27 posted in [问题即经验]

最近一个 PHP 的线上服务经常被报故障,现象是客户端请求一直卡住,类似假死状态。

同事开始排查,我们一开始一直以为是访问量较大引起的性能问题,但是每秒也就个位数级别的 QPS,在一台性能还算 ok 的云服务器上,理论上不应该有性能问题。实际上我们检查之后也排除了 MySQL 死锁、慢查询等可能性,但 nginx 日志里却一直是 499。搜索了一些资料后说明这是因为客户端一直等不到响应的返回,导致超时断开连接。但为何客户端请求会如此慢?再继续排查发现重启 php-fpm 后可以恢复,但过一两趟又出同样的问题,检查日志发现是 php-fpm 的进程资源耗尽。这就很奇怪,正常即使访问量大瓶颈也应该出在没用 SSD 磁盘的数据库 IO 上,但却出现 PHP 的进程问题。最后突然想到可能是后端有访问第三方服务的同步请求,由于目标服务不稳定导致响应变慢才进程排队,且进程池被用完无法接受新请求。对照近期我们依赖的第三方服务机房经常断电的情况,这就能说得通了。但最终的问题其实是代码里发请求的 cURL 部分没有使用超时设置,导致发出去的同步请求一直占用进程没有释放。两行代码解决这个问题:

curl_setopt($curl_handle, CURLOPT_TIMEOUT, 10); // 请求响应超时时间
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 5); // 请求连接超时时间

超时时间设为了 10s,参考微信发给第三方服务的消息超时是 15s,我们一般自身内部请求限制在 5s,这里取了折中值。修改上线以后观察基本没有再出现假死的情况。

可能是在其他 Node.js 的项目默认异步写习惯了,即使某个请求慢也很难耗尽进程,所以从未注意到这样的问题,但 PHP 本身是同步的,即使有文章说有异步的方式但都很麻烦,很少有人这么干。看来还是要等 PHP 升级支持协程以后才会更加方便。

正常来讲第三方服务都要预设为不可靠的,也要预定义好比较常见的异常,尤其是网络连接的异常。很简单的一个处理就可以避免难以排查的问题,其实也是一种设计习惯的经验反映。