背景介绍
在一台线上服务器上,部署了两个服务 A 和 B,A 服务通过 Nginx 处理外部请求,并向本地的 B 服务请求数据。同时,B 服务也接收大量的直接请求。在某些时段内,Nginx 会偶发性地返回 504 请求超时和少量的 502 连接被断开。在此期间,机器的 CPU、内存和 IO 等基础指标均正常,QPS(每秒请求数)仅有微小波动,未超过 100,远低于高峰期的 2800。
通过分析日志,发现从 A 服务到 B 服务的请求链路中,A 服务最后一条日志到 B 服务第一条日志之间的时间间隔长达 10 多秒,暗示请求处理过程中可能出现了超时问题。
问题定位
经过观察,发现与超时日志量波动相对应的唯一指标是 TCP 连接数(CurrEstab),在高峰期间这一指标曾超过 900。初步推测,过高的 TCP 连接数可能导致超时现象的发生。
超时原因分析
PHP 相关信息
- PHP 版本:
PHP 5.6.29 (fpm-fcgi)
PHP-FPM 配置
[global]
pid = /usr/local/services/php-5.6.29/log/php-fpm.pid
error_log = /usr/local/services/php-5.6.29/log/php-fpm.log
[www]
listen = 127.0.0.1:9000
user = user_00
group = users
pm = static
pm.max_children = 200
pm.max_requests = 2000
pm.status_path = /status
ping.path = /ping
ping.response = pong
access.log = log/$pool.access.log
slowlog = log/$pool.log.slow
request_slowlog_timeout = 5s
配置项解析
- pm 参数:设置为 static,适合高内存机器。
pm.max_children
决定了同时可以处理的请求数。 - request_terminate_timeout:若设置为 0 或过长,可能导致 file_get_contents 等请求卡死,建议设置为合理时间以避免长时间等待。
- backlog:定义了 TCP 建立连接时的全连接队列长度,默认值为 65535,实际生效受操作系统设置的限制。
TCP 连接与超时的关系
当 TCP 连接数达到上限时,新的连接将无法建立,导致请求在完成三次握手时未能进入 accept 队列。这种情况下,客户端认为连接已建立,而服务器则未收到相应的 ACK,从而造成重传,最终导致超时。
如何优化 PHP-FPM 配置
设置合适的 backlog 值
根据流量预期与服务承载能力,backlog 的设置应在最大 QPS 的 1-1.5 倍之间。根据测试,建议将 backlog 设置为 4500,以适应高并发。
调整其他重要参数
- max_children:将子进程数调整至 400,以满足极端情况下的请求处理。
- max_requests:设定每个进程的请求处理次数为 25000,减少频繁重启导致的连接错误。
修改系统级别参数
- somaxconn:通过命令或配置文件调整系统级别的 TCP 连接队列大小,设置为 4000。
压测与验证
在新配置下,通过 ab
和 siege
工具对服务进行压力测试,结果显示在 3000 并发请求下,服务稳定无超时,成功处理所有请求。
小结
通过合理配置 PHP-FPM 的参数,优化 TCP 连接处理机制,可以有效避免超时现象的发生。持续监控服务状态和性能指标,将有助于保持高可用性。