早上九点,运营在群里贴截图:页面转圈半分钟,最后跳出 504 Gateway Time-out。翻 nginx 的 error.log,对应的那行写着 upstream timed out (110: Connection timed out) while reading response header from upstream。
先分清责任:504 是网关自己报的,意思是「我等上游等到超时」,不是上游返回了 504。查的方向在 nginx 和后端应用之间,跟浏览器没关系。
先在服务器上量一次真实耗时
绕开 nginx 直接打后端:curl -o /dev/null -s -w '%{time_total}' http://127.0.0.1:9000/api/order。这里也要几十秒,问题在应用;这里两百毫秒就回来,问题在 nginx 到应用这一段,重点看连接数、keepalive 和 DNS 解析。
nginx 的 access.log 配上 upstream_response_time 和 request_time 两个字段,一眼能分出是上游慢还是客户端慢。log_format 里加上这两个变量,reload 一次就生效。
三个超时参数卡的是不同阶段
- proxy_connect_timeout:跟上游建 TCP 连接的时间,默认 60s,内网可以收到 5s
- proxy_read_timeout:连接建立后等响应的间隔,默认 60s,长轮询接口单独放大
- proxy_send_timeout:往上游写请求的时间,上传大文件才会撞上
应用侧还有自己的闸门:PHP-FPM 的 request_terminate_timeout、Gunicorn 的 --timeout 30、Tomcat 的 connectionTimeout。任何一层先断,nginx 都会记成 504 或者 502。
排到最后通常是这几类原因
慢 SQL 没走索引,几十万行的表扫了全表;第三方接口挂了,本地同步调用又没设超时;数据库连接池被打满,请求在应用里排队;容器内 DNS 解析抽风,解析一次卡几秒。
把这些排完,再决定是调 proxy_read_timeout 还是把接口改成异步任务加轮询。光把 60s 改成 300s,用户不会等,只是把报错换成了更久的转圈。