LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

Nginx 502 网关错误连环排查:从 upstream 超时到连接池耗尽

admin
2026年9月23日 18:27 本文热度 32

引言

周三下午三点,客服群开始零星弹出同一句话:"系统又打不开了,刷新一下又好了。" 监控上看服务实例全是绿色、健康检查通过率 100%、CPU 内存都正常,但 Nginx 的 502 曲线每隔十几分钟就冒一个小尖峰。我们登录服务器查后端应用日志——一片平静,ERROR 一个没有,连对应的访问记录都找不到。

这就是 502 类问题最迷惑的地方:报错发生在 Nginx,但根因往往不在 Nginx;后端日志干干净净,不是因为没问题,而是请求压根没走到(或没走完)业务代码。 这次排查从一行 upstream timed out 出发,顺着四层往下挖:Nginx 层超时 → JVM 层发现 GC 停顿 → 线程层发现请求排队 → 连接池层发现连接被慢 SQL 占满,最终拼出完整根因链。这篇文章把这条链路、每一步的排查命令、以及修复时的超时层级设计完整复盘。


一、现场:偶发 502,"偶发"才是最难的部分

1.1 故障特征

观察项
现象
发生频率
工作日下午集中,十几分钟一次,每次持续几秒到几十秒
用户体感
页面转圈很久 → 502,立刻重试大概率成功
Nginx 监控
502 率平时 0,尖峰时 0.3%~1%
后端监控
实例存活、健康检查 100%、CPU 40%、内存 60%
后端日志
应用 ERROR 日志为零,甚至没有该请求的访问日志
接口分布
不是某个固定接口 502,故障窗口内各接口都有

"所有接口都零星 502 + 应用无日志 + 能自愈"——这三个特征组合在一起,基本可以排除业务代码抛异常(那会有 500 和堆栈),指向请求在到达业务逻辑之前或执行过程中被整体卡住了

1.2 第一现场:Nginx error.log

tail -f /var/log/nginx/error.log | grep -v 'favicon'

抓到关键日志:

2026/09/16 15:17:42 [error] 1234#1234: *5678901 upstream timed out (110: Connection timed out)
while reading response header from upstream,
client: 10.2.3.4, server: admin.example.com,
request: "GET /api/order/list?page=1 HTTP/1.1",
upstream: "http://10.2.4.15:8080/api/order/list?page=1",
host: "admin.example.com"

逐字段解读:

日志片段
含义
指向
upstream timed out
Nginx 等上游响应超时
问题在后端,不在 Nginx 本身
while reading response header
卡在等响应头——后端一个字节的响应行都没返回
请求要么没被处理,要么处理过程整体卡住
(110: Connection timed out)
超时类型是读超时(不是 connection refused)
不是后端宕机/端口不通(那会是 502 connect failed)

顺带建立 502/504 的直觉:

connect() failed (111: Connection refused)  → 后端进程没了/没监听(真挂了)
upstream timed out while connecting        → 建连阶段超时(backlog 满/网络)
upstream timed out while reading response  → 连上了但等不到响应(本文场景)
upstream prematurely closed connection     → 后端主动断连(重启/keepalive 时间错配/崩了)

1.3 看 Nginx 的超时配置

location /api/ {
    proxy_pass http://backend;
    proxy_connect_timeout 3s;     # 建 TCP 连接超时
    proxy_send_timeout    10s;    # 向后端发请求体的超时
    proxy_read_timeout    30s;    # ★ 两次读操作之间的最大间隔,30 秒没读到任何字节就 502
}

proxy_read_timeout 30s 是两个读事件之间的间隔阈值,不是总时长——但如果后端 30 秒内一个字节都没吐(线程还在排队等连接),就会精确触发。用户"转圈很久然后 502"的体感与 30 秒完全吻合。


二、第一跳:应用没日志,但请求去哪了

2.1 复现窗口里看 Tomcat 线程在干什么

故障窗口抓一次线程栈(jstack 连续抓 3 次,间隔 5 秒,判断线程是真卡死还是瞬时慢):

jstack -l <pid> > /tmp/thread_1.txt; sleep 5; \
jstack -l <pid> > /tmp/thread_2.txt; sleep 5; \
jstack -l <pid> > /tmp/thread_3.txt

统计线程状态:

grep 'java.lang.Thread.State' /tmp/thread_1.txt | sort | uniq -c | sort -rn
#  187    java.lang.Thread.State: WAITING (on object monitor)
#   12    java.lang.Thread.State: RUNNABLE
#    1    java.lang.Thread.State: TIMED_WAITING (parking)

Tomcat 工作线程池 maxThreads=200,此刻 187 个 WAITING。随便展开一个:

"http-nio-8080-exec-143" #341 daemon prio=5 os_prio=0 waiting on condition
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park(...)
        at java.util.concurrent.locks.LockSupport.parkNanos(...)
        at com.zaxxer.hikari.util.ConcurrentBag.borrow(ConcurrentBag.java:...)
        at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:...)
        at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:...)
        at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:...)
        at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(...)
        at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(...)
        at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(...)
        at com.xxx.order.mapper.OrderMapper.selectPage(...)            ← 请求卡在这
        ...

三次抓栈中,大量 http-nio 线程全部卡在同一个位置:HikariPool.getConnection——排队等数据库连接。真相浮出一半:请求到了 Tomcat,占了工作线程,但在拿 DB 连接这一步排队,应用代码没执行到任何一行有业务日志的地方——这解释了"后端没有异常日志"。

2.2 为什么用户"偶发":排队的累积与释放

正常:200 工作线程,20 个 DB 连接,请求毫秒级拿到连接归还,毫无压力
故障:某条 SQL 变慢(从 5ms 变成 8s)→ 连接被占用时间拉长 1600 倍
     → 20 个连接逐渐全被慢请求占住
     → 后续 180 个请求在 Hikari 池外排队(占着 Tomcat 线程干等)
     → 排队超过 30s 的请求被 Nginx 判 502
     → 慢 SQL 执行完一批,连接释放,队列瞬间清空 → "重试又好了"

所以"偶发 502"的本质是池化资源的排队溃塌:不是每个请求都慢,而是排队位置靠后的那批请求超时。故障窗口里连接池的"等待-耗尽-释放"循环,正好对应 502 曲线的尖峰。

2.3 第二层现象:GC 暂停让排队雪上加霜

排查中还发现一个加剧因素:GC 日志里故障窗口有异常长的停顿:

# 开启 GC 日志(JDK 17)
-Xlog:gc*,safepoint:file=/var/log/app/gc-%t.log:time,uptime,level,tags:filecount=10,filesize=50M
[2026-09-16T15:17:35.201+0000] Pause Young (G1 Evacuation Pause) 4120M->2100M(4096M) 1.823ms
[2026-09-16T15:17:41.884+0000] Pause Full (G1 Compaction Pause) 4010M->1980M(4096M) 926.412ms  ★

一次 Full GC 停顿 926ms,期间所有应用线程冻结:正在执行的 SQL 停止发网络包、连接池归还全部暂停。这 1 秒内:

  • Nginx 侧:所有在途请求的 read_timeout 计时器继续走,响应间隔被拉大;
  • DB 侧:连接还占着但没有任何动作;
  • GC 结束后被推迟的请求一窝蜂恢复,瞬间争抢连接,把排队峰值推过 30 秒红线。

GC 停顿不是根因(没有慢 SQL 时 1 秒停顿不致命),但它是放大器——它解释了为什么尖峰总是集中在某些时刻,以及为什么连接池告警和 502 的时间点能对上 Full GC。


三、根因:连接池为什么会被占满

3.1 20 个连接去哪了:抓数据库侧现场

应用栈只能看到"在排队",要确认"连接被谁占着"得查数据库:

-- MySQL:看当前所有连接的状态和已执行时间
SELECT iduser, db, command, time, state,
       LEFT(info, 200AS sql_text
FROM information_schema.processlist
WHERE command != 'Sleep' OR time > 10
ORDER BY time DESC;

故障窗口的结果(截取):

id    time   state                                          sql_text
8821  18.40  Sending data                                  SELECT ... FROM order_item oi
                                                                 LEFT JOIN order o ON ...
                                                                 LEFT JOIN product p ...
                                                              WHERE o.tenant_id = 5 ...
8835  17.92  Sending data                                  (同上,另一个分页请求)
8840  17.31  Sending data                                  (同上)
... 共 14 条同类 SQL,执行时长 8~20 秒
8860  9.02   Waiting for table metadata lock               ALTER TABLE ...(白天跑的 DDL!)

3.2 慢 SQL 是怎么来的:一条新加的报表查询

查慢查询日志确认:

-- my.cnf
slow_query_log = ON
long_query_time = 1
log_queries_not_using_indexes = ON
# Query_time: 18.40  Lock_time: 0.00  Rows_sent: 20  Rows_examined: 2480000
SELECT ... FROM order_item oi LEFT JOIN order o ON ... LEFT JOIN product p ...
WHERE o.tenant_id = 5 ORDER BY oi.created_at DESC LIMIT 20;
  • Rows_examined: 248 万Rows_sent: 20:典型的大范围扫描+排序,分页深翻页;
  • 前一天发布的新功能"订单明细分页导出",三表 LEFT JOIN 缺少 (tenant_id, created_at) 联合索引;
  • 更糟的是同时段有一条 metadata lock 等待 9 秒:白天执行的 DDL 被长事务堵住,又反过来堵住后续要拿元数据锁的查询——锁等待与慢 SQL 叠加。

3.3 完整根因链

┌─ 诱因 ────────────────────────────────────────────────────────┐
│ 新发布报表 SQL 缺索引,单次执行 8~20 秒(平时 5ms)             │
└──────────────────────────┬────────────────────────────────────┘
                           ▼
┌─ 资源层 ──────────────────────────────────────────────────────┐
│ DB 连接被慢 SQL 长时间持有(20 个 Hikari 连接 × 8s = 极低吞吐)│
│ + 白天 DDL 的 metadata lock 等待进一步拉长占用时间              │
└──────────────────────────┬────────────────────────────────────┘
                           ▼
┌─ 应用层 ──────────────────────────────────────────────────────┐
│ HikariPool 20 个连接全部 active → 新请求 borrow 排队            │
│ 187 个 Tomcat 线程 WAITING 在 getConnection(不报错、无日志)   │
│ Full GC 0.9s 停顿冻结归还动作,把排队峰值推过红线(放大器)      │
└──────────────────────────┬────────────────────────────────────┘
                           ▼
┌─ 网关层 ──────────────────────────────────────────────────────┐
│ 排队靠后的请求 30 秒内一个响应字节都没吐                        │
│ Nginx proxy_read_timeout 触发 → upstream timed out → 返回 502 │
│ 慢请求陆续完成、连接释放、队列清空 → 重试成功(故障自愈)        │
└───────────────────────────────────────────────────────────────┘

为什么监控和健康检查没报? 健康检查接口 /actuator/health 不查数据库或查得极快、不占业务连接池;实例进程存活、健康探针永远 200。存活探针只能证明"进程在",证明不了"能正常服务"。


四、修复:三层一起动,单层修复都会复发

4.1 第一层:消灭慢 SQL(治本)

-- ① 补联合索引(顺序:等值过滤列在前,排序列在后)
ALTER TABLE `order` ADD INDEX idx_tenant_created (tenant_id, created_at);
ALTER TABLE order_item ADD INDEX idx_order_id (order_id);

-- ② 深分页改造:先用覆盖索引定位主键,再回表
-- 改写前(翻到第 5000 页扫 10 万行)
SELECT oi.* FROM order_item oi JOIN `order` o ON ...
WHERE o.tenant_id = 5 ORDER BY oi.created_at DESC LIMIT 10000020;

-- 改写后(游标分页)
SELECT oi.* FROM order_item oi JOIN `order` o ON ...
WHERE o.tenant_id = 5 AND oi.created_at < #{lastCreatedAt}
ORDER BY oi.created_at DESC LIMIT 20;

效果:18.4s → 23ms。同时立规矩:DDL 全部走 gh-ost / pt-online-schema-change 并安排在低峰,禁止业务高峰期直接 ALTER(metadata lock 连锁)。

4.2 第二层:给连接池和 SQL 装上超时(止血,防止任何慢 SQL 再拖垮全局)

光优化这一条 SQL 不够——下次别的 SQL 变慢,同样的故事还会重演。必须让"慢"有上限:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      # ① 获取连接最多等 3 秒:快速失败,而不是无限排队拖过 Nginx 30s
      connection-timeout: 3000
      validation-timeout: 1000
      # ② 连接最大生命周期,防止持有陈旧连接
      max-lifetime: 1800000
      # ③ 连接泄漏检测:借出超过 10s 未还,打印堆栈(排查利器,生产可常开)
      leak-detection-threshold: 10000

SQL 执行本身也要有超时(连接池只管"借",管不住"借到之后一条 SQL 跑多久"):

# MyBatis:全局 statement 超时 5 秒(慢查询在数据库侧被 kill,连接立刻释放)
mybatis:
  configuration:
    default-statement-timeout: 5
# 或在 Mapper 上单独标注:@Options(timeout = 3)

需要更长时间的报表/导出接口单独放宽并走独立线程池+独立连接池,绝不能让慢查询和在线交易共用同一个池

4.3 第三层:Nginx 重试与超时层级(兜底体验)

重试只对幂等请求开启,否则重试 POST 会造成重复下单:

upstream backend {
    server 10.2.4.15:8080 max_fails=2 fail_timeout=10s;
    server 10.2.4.16:8080 max_fails=2 fail_timeout=10s;
    keepalive 64;                      # 复用长连接,减少握手开销
}

location /api/ {
    proxy_pass http://backend;

    # ① 只在"连接失败/超时且没发出请求"时重试,防止把请求体发两遍
    proxy_next_upstream error timeout http_502 http_504;
    proxy_next_upstream_tries 2;       # 含首次最多尝试 2 次
    proxy_next_upstream_timeout 5s;    # 重试总时间上限

    # ② POST 默认不在重试范围;如需对幂等 POST 重试显式开启
    # proxy_next_upstream_non_idempotent on;   # ★ 确认接口幂等才开!

    proxy_connect_timeout 3s;
    proxy_read_timeout    10s;
    proxy_send_timeout    10s;
}

注意 proxy_next_upstream 默认包含 error/timeout,对 GET/HEAD 天然安全;对 POST,Nginx 认为是 non-idempotent 默认不重试——这个默认值不要轻易改,订单类接口重复提交就是资损。本文场景中读接口(GET /api/order/list)在一台实例超时时自动转发到另一台健康实例,用户连 502 都看不到。

4.4 关键:超时必须层层递减,禁止倒挂

事故配置的致命问题是各层超时各配各的:

事故时(倒挂):
  单条 SQL 无超时(可跑 20s+)
  Hikari connectionTimeout 30s(排队 30s)
  Tomcat 无整体超时
  Nginx proxy_read_timeout 30s
  → 用户先看到 502,服务端线程还在傻等,连接继续被占,雪崩放大

修复后(递减,快速失败向上传播):
  SQL statement timeout   5s   ← 最慢的 SQL 5 秒被杀,连接立即归还
  Hikari connectionTimeout 3s  ← 借不到连接 3 秒快速失败(Tomcat 收到异常)
  服务整体响应目标         8s
  Nginx proxy_read_timeout 10s ← 留给后端返回错误 JSON 的余量

原则:越靠近资源的超时越短,让每一层都有机会在上一层放弃之前产出明确的失败响应。超时倒挂(下游超时 > 上游)是网关类故障的万能放大器。

4.5 补监控:让故障在用户投诉前可见

这次问题全程靠人肉抓栈,根因是监控缺失。补齐三类指标:

# HikariCP 指标(Micrometer 自动暴露 hikaricp.connections.*)
management:
  endpoints:
    web:
      exposure:
        include: prometheus,health
# ① 连接池等待线程 > 0 持续 1 分钟(排队的直接信号,比 502 早 10 分钟出现)
hikaricp_connections_pending{app="order-service"} > 0

# ② 连接池使用率 100% 持续 2 分钟
hikaricp_connections_active{app="order-service"}
  / hikaricp_connections_max{app="order-service"} >= 1

# ③ JVM 停顿超过 500ms(GC 放大器预警)
rate(jvm_gc_pause_seconds_sum{action="end of major GC"}[1m]) > 0.5

# ④ Nginx 502 率(用户侧最终信号)
sum(rate(nginx_http_responses_total{status=~"5.."}[1m]))
  / sum(rate(nginx_http_responses_total[1m])) > 0.005

告警 ①② 会比 502 早 5~10 分钟出现——连接池排队是这条根因链上最早的可观测信号。另外健康检查增加"深度检查"的可选探针(带连接池可用性,但不挂存活探针、挂就绪探针,避免摘节点时反复抖动)。


五、修复效果与验证

指标
修复前
修复后
报表 SQL 平均耗时
8~20s
20~50ms
Hikari pending 线程
峰值 180+
0
连接池 100% 占用持续时长
单次 20~40s
0(statement timeout 兜底最多 5s)
502 率(下午高峰)
峰值 1%
0(一周观察)
Full GC 频率
高峰偶发 0.9s
调整堆参数+大对象排查后 <100ms
故障发现方式
用户投诉
连接池 pending 告警提前 10 分钟

验证手段:预发环境用慢查询注入(SELECT ... SLEEP() 占满连接池)+ 压测,确认三层行为符合预期——SQL 5 秒被杀、Hikari 3 秒快速失败、接口返回 503 业务错误而不是挂起、Nginx GET 请求自动重试到第二实例、POST 不重试。故障注入演练比生产观察更可靠


六、通用排查 SOP:下次 502 按这个顺序走

Step 1 Nginx error.log 确认超时类型
        reading response header → 后端慢(本文)
        connect refused/reset   → 后端进程/keepalive
Step 2 故障窗口 jstack ×3(间隔5秒),统计线程状态分布
        大批 WAITING 在 HikariPool.getConnection → 连接池问题
        RUNNABLE 卡在 socketRead 外部 HTTP/RPC   → 下游依赖慢
        RUNNABLE 卡在业务代码                    → CPU/锁/算法
Step 3 DB processlist / pg_stat_activity 确认连接被什么 SQL 占着、占多久
Step 4 慢查询日志/pg_stat_statements 定位具体 SQL,EXPLAIN 看执行计划
Step 5 对照 GC 日志确认停顿是否为放大器
Step 6 检查四层超时是否倒挂:statement < pool < service < nginx
Step 7 修复后故障注入验证,补 pending 连接数告警

七、常见问题

7.1 Nginx 502 和 504 到底有什么区别?重试策略一样吗?

502 Bad Gateway:网关从上游收到了无效响应或连接被异常关闭(refused/reset/premature close 也常映射为 502);504 Gateway Timeout:网关在规定时间内没等到响应。部分 Nginx 版本/配置下 upstream read timeout 会返回 504 而非 502(取决于是否已经回写了响应头),排查方法完全相同——都看 error.log 里的具体阶段(connecting/reading header/reading body)。重试安全性只取决于请求幂等性,与 502/504 无关。

7.2 连接池是不是调大就行?20 太小了吧?

连接池大小不是越大越好,这是最常见的误区。数据库里 20 个慢查询变成 200 个慢查询只会让 DB CPU/IO 更早崩,而且每个连接在 MySQL 都吃内存(PG 更是每连接一个进程)。Hikari 官方建议 pool size = ((核心数×2) + 有效磁盘数),OLTP 场景通常 10~30 足够。正确方向是让 SQL 变快、让等待快速失败,而不是放大池子掩盖排队——池子越大,雪崩时堆积的请求越多、恢复越慢。

7.3 为什么不把 Nginx proxy_read_timeout 调大到 60 秒让它等?

调大网关超时只是让用户对着转圈看更久,不解决任何资源问题,反而让排队请求堆积更久、雪崩范围更大。网关超时的意义是"快速给用户一个确定结果,释放两端资源"。正确做法永远是下游超时小于网关超时(本文 5s SQL / 3s 借连接 / 10s Nginx),让请求快速失败并带着业务错误码返回,前端可以提示"系统繁忙请重试",而不是干等 30 秒后收到一个裸 502。

7.4 Tomcat 线程池本身要不要也加超时/限流?

要,这是本文方案的补充:① 给 Tomcat 配 acceptCount 和合理的 maxThreads,请求在 TCP backlog 阶段就排队过长时快速拒绝,比让请求进来占内存更好;② 入口加 Sentinel 并发线程数限流(参见之前 Sentinel 篇),当活跃请求超过容量直接返回 429,是比连接池排队更靠前的一道闸门;③ 慢操作(报表/导出/批量)独立线程池+独立连接池,线程池隔离(舱壁模式)保证慢业务不拖垮在线交易。

7.5 健康检查都是绿的,怎么让 K8s/负载均衡感知到这种故障?

区分三种探针:存活探针(进程在,永远只查 liveness,别加依赖检查否则会误杀)、就绪探针(可以检查连接池能否获取连接、DB 是否可达,失败则摘流不杀容器)、启动探针。就绪探针里加一个"500ms 内能否拿到连接"的检查,连接池耗尽时实例自动从负载均衡摘除,Nginx 重试只打健康实例。但注意就绪检查本身要设短超时且不占用主池连接(可用独立探测连接或只看 Hikari 的活跃指标),防止检查本身加剧排队。

7.6 已经有全链路 Trace 了,为什么 jstack 和 processlist 还是不能少?

Trace 记录的是"请求走过的跨度耗时",但请求卡在 Hikari 排队时,Span 还没开始(没有 DB span);卡在 GC 冻结时所有时钟都停——Trace 上只会看到一个巨大的空隙,告诉你"这段时间什么都没发生",但解释不了为什么。jstack 回答"线程此刻在哪行代码上等待"、processlist 回答"连接此刻在执行什么 SQL"、GC log 回答"JVM 有没有冻结"——这三类是瞬时状态证据,与 Trace 的因果链互补。502 类问题的标配证据链永远是:Nginx 日志 + 线程栈 + DB 现场 + GC 日志四件套。


八、总结

排查与修复速查卡

证据链四件套:
  Nginx error.log(超时阶段)→ jstack ×3(卡在哪行)
  → DB processlist(连接被谁占)→ GC log(有没有停顿放大)
根因链:
  慢SQL → 连接持有时间暴增 → 池耗尽 → Tomcat线程排队
       → GC停顿放大 → 30秒无响应 → Nginx 502 → 连接释放自愈
修复三层:
  ① 治本:慢 SQL 加索引/改写 + DDL 低峰在线变更
  ② 止血:statement timeout 5s + connectionTimeout 3s + leakDetection
          慢接口独立连接池;池不盲目调大
  ③ 兜底:Nginx 幂等 GET 重试(POST 默认不重试)+ 超时层层递减
超时黄金链:SQL 5s < 借连接 3s < 服务 8s < Nginx 10s
监控先行:hikaricp_connections_pending > 0 是最早信号,比 502 早 10 分钟

一句话

偶发 502 的排查永远从 Nginx error.log 里那行 upstream timed out 开始,但答案一定不在 Nginx:当日志写着 while reading response header,说明连接建立了、请求发出了,后端却一个字节都没吐——此时不要重启碰运气,故障窗口连续 jstack 三次,你会看到大批 Tomcat 线程以同样的姿势 WAITING 在 HikariPool.getConnection,应用没有 ERROR 日志不是因为系统健康,而是请求在执行业务代码之前就卡在了资源排队上;再查数据库 processlist,占着连接的必然是少数几条执行了十几秒的慢 SQL(缺索引的三表 JOIN、白天 DDL 的 metadata lock),连接被它们长期持有、池被打满、新请求排队,叠加一次近 1 秒的 Full GC 冻结让归还停滞,排队靠后的请求 30 秒内零响应字节,被 proxy_read_timeout 判死,而慢请求完成、连接释放后队列瞬间清空,于是用户重试又好——这就是"偶发、自愈、无日志"三个迷惑特征的全部解释。修复必须三层联动:治本是给慢 SQL 补索引、改游标分页、DDL 走低峰在线变更;止血是让每一层等待都有上限(statement timeout 5 秒杀慢查询释放连接、connectionTimeout 3 秒快速失败、leak-detection 抓泄漏、慢接口独立池隔离),而不是把连接池从 20 调到 200 去掩盖排队;兜底是 Nginx 只对幂等 GET 开启 proxy_next_upstream 重试(POST 默认不重试,防重复下单),并保证超时层层递减——SQL 5s < 连接池 3s < 服务 8s < Nginx 10s——倒挂的超时配置是所有网关雪崩的放大器。最后把 hikaricp_connections_pending 告警配上,连接池开始排队比用户看到 502 早整整十分钟,能在告警里看见排队的系统,才不需要在投诉电话里解释 502。

给团队的建议

建议
排查
四件套固定动作:Nginx 日志 → jstack×3 → DB processlist → GC 日志
SQL
上线前 EXPLAIN 审核,慢查询日志 long_query_time=1 常开
超时
任何外部资源调用必须有超时,按"越靠近资源越短"配置
池化
池大小保持小而快,慢业务独立池;pending 指标必告警
变更
DDL 低峰 + 在线 DDL 工具,禁止高峰期直改表结构
重试
只重试幂等请求,非幂等接口靠幂等键保证安全
探针
就绪探针检查依赖可用性,存活探针只查进程
演练
预发故障注入(慢 SQL 占池)验证超时/重试/摘除行为

互动话题:你们遇到的 502 最后根因是什么?慢 SQL、GC、连接池还是 keepalive 配置?排查花了多久?评论区交流。


参考资料

  • Nginx 官方文档:proxy_next_upstream 与超时指令
  • HikariCP 官方 Wiki:池大小建议与超时配置
  • MySQL 官方:information_schema.processlist 与元数据锁
  • Oracle 文档:G1 GC 停顿调优与 GC 日志
  • Google SRE Book:Addressing Cascading Failures(级联失败与快速失败)


阅读原文:点击这里


该文章在 2026/9/23 18:27:34 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号