引言
周三下午三点,客服群开始零星弹出同一句话:"系统又打不开了,刷新一下又好了。" 监控上看服务实例全是绿色、健康检查通过率 100%、CPU 内存都正常,但 Nginx 的 502 曲线每隔十几分钟就冒一个小尖峰。我们登录服务器查后端应用日志——一片平静,ERROR 一个没有,连对应的访问记录都找不到。
这就是 502 类问题最迷惑的地方:报错发生在 Nginx,但根因往往不在 Nginx;后端日志干干净净,不是因为没问题,而是请求压根没走到(或没走完)业务代码。 这次排查从一行 upstream timed out 出发,顺着四层往下挖:Nginx 层超时 → JVM 层发现 GC 停顿 → 线程层发现请求排队 → 连接池层发现连接被慢 SQL 占满,最终拼出完整根因链。这篇文章把这条链路、每一步的排查命令、以及修复时的超时层级设计完整复盘。
一、现场:偶发 502,"偶发"才是最难的部分
1.1 故障特征
| |
|---|
| 工作日下午集中,十几分钟一次,每次持续几秒到几十秒 |
| |
| |
| 实例存活、健康检查 100%、CPU 40%、内存 60% |
| 应用 ERROR 日志为零,甚至没有该请求的访问日志 |
| |
"所有接口都零星 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 | | |
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 计时器继续走,响应间隔被拉大;
- GC 结束后被推迟的请求一窝蜂恢复,瞬间争抢连接,把排队峰值推过 30 秒红线。
GC 停顿不是根因(没有慢 SQL 时 1 秒停顿不致命),但它是放大器——它解释了为什么尖峰总是集中在某些时刻,以及为什么连接池告警和 502 的时间点能对上 Full GC。
三、根因:连接池为什么会被占满
3.1 20 个连接去哪了:抓数据库侧现场
应用栈只能看到"在排队",要确认"连接被谁占着"得查数据库:
-- MySQL:看当前所有连接的状态和已执行时间
SELECT id, user, db, command, time, state,
LEFT(info, 200) AS 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 100000, 20;
-- 改写后(游标分页)
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 分钟出现——连接池排队是这条根因链上最早的可观测信号。另外健康检查增加"深度检查"的可选探针(带连接池可用性,但不挂存活探针、挂就绪探针,避免摘节点时反复抖动)。
五、修复效果与验证
| | |
|---|
| | |
| | |
| | 0(statement timeout 兜底最多 5s) |
| | |
| | |
| | |
验证手段:预发环境用慢查询注入(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 日志 |
| 上线前 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 编辑过