那天中午,监控突然告警:线上服务大面积返回 504 Gateway Timeout。查看 Nginx 日志,大量请求耗时超过 60 秒后被强制断开。问题是,平时同样的接口响应都在 200ms 以内。
排查过程
第一步查看 PHP-FPM 的慢日志,发现大量请求卡在同一个位置——一个调用第三方短信服务的 cURL 请求。登录服务器后手动 curl 那个第三方接口,居然要 45 秒才返回。原来是对端服务出了故障,但我们的代码没有设置任何超时!
关键问题在于:PHP 的 cURL 默认超时是无限等待。也就是说,如果对端服务不响应,cURL 会一直阻塞直到连接被操作系统层面断开。在生产环境中,这会导致 PHP-FPM 工作进程被耗尽,后续请求全部排队,最终触发 Nginx 的 proxy_read_timeout 返回 504。
修复方案
修复很简单——给所有 cURL 请求加上超时设置:
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 总超时
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // 连接超时
同时加上重试机制:失败后最多重试 2 次,每次间隔递增。重要的业务调用加上熔断逻辑——如果连续失败超过阈值,暂时跳过该服务并告警。
教训
这次事故让我深刻意识到:任何涉及网络 I/O 的操作都必须设置超时。不仅仅是 cURL,数据库连接、Redis 连接、文件读写——所有外部依赖都要有超时兜底。另外,完善的服务降级策略能在第三方出问题时保住核心业务的可用性。