深色模式
依赖雪崩排查
摘要:雪崩的特征是"一个依赖慢,全家都不可用"。本文教你在多服务同时告警时找出真正的源头,理解重试放大与线程池占满的传导机制,并给出熔断、隔离、超时递减三项止损原则。
适用环境
bash
curl -V | head -1
which jstack 2>/dev/null
grep -rE 'timeout|retry' /etc/app/*.yml 2>/dev/null | head -201
2
3
2
3
排障步骤
第 1 步:画出调用关系并确定起点
告警往往会同时命中上下游,必须先看清楚是谁先异常:
bash
# 对比各服务的错误率开始上升的时间点
kubectl logs -l app=<svc-a> --since=30m --timestamps | grep -i 'timeout' | head -5
kubectl logs -l app=<svc-b> --since=30m --timestamps | grep -i 'timeout' | head -51
2
3
2
3
错误最早出现的那个服务,才是传导链的下游起点(最底层)。
第 2 步:验证底层依赖是否真的慢
bash
curl -sS -m 3 -o /dev/null -w 'code=%{http_code} total=%{time_total}\n' http://<依赖地址>/health
ss -antp state established '( dport = :<依赖端口> )' | wc -l1
2
2
第 3 步:检查线程池是否被慢依赖占满
bash
jstack -l <PID> | grep -c 'http-nio'
jstack -l <PID> | grep -B 3 'http.client|socketRead' | head -401
2
2
所有工作线程卡在同一个下游调用上,说明该依赖已把线程池吃满,进而导致与该依赖无关的接口也开始超时——这是雪崩的关键传导环节。
第 4 步:核对超时是否倒挂
bash
grep -rE 'timeout|connectTimeout|readTimeout' /etc/app/*.yml /etc/nginx/conf.d/*.conf 2>/dev/null1
超时倒挂是雪崩放大器
调用方 2 秒就放弃,被调方还在算 30 秒,请求以十几倍速度堆积。配置原则是:网关超时 > 服务超时 > 依赖超时,逐层递减。
第 5 步:检查重试放大
bash
grep -rE 'retry|maxAttempts|retries' /etc/app/*.yml /etc/nginx/conf.d/*.conf 2>/dev/null1
网关、SDK、业务代码三层各自重试 3 次,最坏情况放大 27 倍,会把"慢"直接压成"死"。
第 6 步:止损手段
- 熔断:依赖错误率超阈值后直接快速失败,不再消耗线程
- 隔离:为不同依赖分配独立线程池/信号量,避免相互拖垮
- 降级:返回兜底数据或关闭非核心功能
- 限流:在入口限制进入的请求量
bash
# 先在网关层摘掉问题依赖对应的非核心接口,降低整体负载
grep -rn 'location' /etc/nginx/conf.d/*.conf | head -201
2
2
验证
bash
curl -sS -m 5 -o /dev/null -w 'code=%{http_code} total=%{time_total}\n' http://127.0.0.1:8080/api
jstack -l <PID> | grep -c 'WAITING'
ss -ant state established | wc -l1
2
3
2
3
常见坑
跟着告警数量找根因是靠不住的
被拖垮的服务告警最多,但根因在被调用方。应按时间顺序而非告警数量定位。
只看自己的服务日志
雪崩是跨服务现象,必须串联调用链(trace id 或至少有统一时间戳)才能看全。
熔断后立刻全量恢复
半开状态下若依赖未恢复就放开全量流量,会造成二次雪崩。应逐步放量。
通过重启全部服务"清状态"
雪崩时批量重启会瞬间产生大量重建连接与缓存冷启动压力,反而加速恶化。应有节奏地重启并先限流。