性能调优与排障
1. 这是什么
前面几章学完之后,你大概已经知道 Dubbo 是怎么工作的了。
但一到真实线上环境,问题通常不会按章节来:
- 某个接口突然超时
- 某个服务错误率升高
- 某个 Consumer 越重试越糟
- 某台 Provider CPU 很高
- 明明网络通,调用还是慢
这时候就进入了 Dubbo 最实战的一章:
- 性能调优:让调用更快、更稳、更省资源
- 排障:当调用出问题时,快速找到真正瓶颈
如果只用一句话概括:
Dubbo 排障不是只看一个报错,而是顺着整条 RPC 链路找哪一层出了问题。
2. 为什么重要
RPC 问题之所以难排,是因为它天然跨多个层次:
- Consumer 侧配置
- 注册发现
- 路由与负载均衡
- 网络传输
- 序列化/反序列化
- Provider 业务执行
- 线程池
- 下游数据库/缓存/第三方依赖
所以你看到一个“超时”,背后可能完全不是同一个原因:
- 可能是网络慢
- 可能是下游 SQL 慢
- 可能是线程池满了
- 可能是重试把链路拖长了
- 可能是序列化对象太大
这也是为什么 RPC 排障最忌讳:
- 一看到超时,就先把超时值调大
因为这常常只是把问题藏起来,而不是解决它。
3. 先建立排障总思路
你可以把一次 Dubbo 调用拆成 6 段:
- Consumer 发起调用
- 服务发现与选路
- 网络发送
- Provider 接收并执行业务
- Provider 返回结果
- Consumer 收到并解析响应
任何一段慢了,最终都可能表现成:
- 超时
- 错误率高
- RT 飙升
所以排障时不要只问:
- “这个接口为什么超时?”
而要进一步追问:
- “它到底慢在链路哪一段?”
这个思路非常关键。
4. 最常见问题 1:超时
超时是 Dubbo 排障里最常见的表象之一。
但“超时”不是根因,只是结果。
常见根因可能包括
- Provider 业务逻辑慢
- 下游数据库查询慢
- Provider 线程池积压
- 网络抖动
- 请求体/响应体过大
- 重试叠加导致总耗时过长
- Consumer 超时配置过小
一句实战经验
先找哪段慢,再决定调不调超时;不要先调超时,再假装问题解决。
5. 最常见问题 2:重试放大故障
很多系统在下游开始抖动时,会出现一种典型现象:
- 本来只是少量请求变慢
- Consumer 开启重试后
- 请求数被放大
- 下游更扛不住
- 最后整个链路一起雪崩
这就是所谓的“重试放大问题”。
例如:
- 原本 1000 QPS
- 每个失败请求再重试 2 次
- 实际打到下游的流量可能瞬间变成数倍
所以重试不是默认收益,而是默认带风险。
特别是:
- 下游本来就已经慢
- 线程池已经拥堵
- 数据库已经告警
这时继续重试,往往是火上浇油。
6. 最常见问题 3:线程池和请求积压
Provider 侧如果线程池满了,会出现什么?
- 新请求排队
- 响应时间变长
- 超时增多
- CPU 未必很高,但整体调用已经很差
这是很多人容易误判的点。
有些时候问题不是:
- 机器不行
而是:
- 线程模型不合适
- 队列太长
- 任务执行时间太久
所以看 Dubbo 性能时,不能只看:
- CPU
- 内存
还要看:
- 线程池活跃数
- 队列积压
- 请求等待时间
7. 最常见问题 4:序列化和数据体积
很多人排查慢调用时,只盯业务代码,却忽略:
- 请求对象是不是太大
- 返回对象是不是太复杂
- 序列化是不是很重
例如:
- 一个接口返回几十 KB、几百 KB 的对象
- 嵌套层级很多
- 字段冗余严重
这会带来:
- 序列化成本升高
- 网络传输时间增加
- 反序列化更慢
所以优化 RPC 不一定只是优化 SQL,有时候也要先问:
- 这个对象真的要传这么多字段吗?
8. 最常见问题 5:网络没断,但就是慢
RPC 问题不一定是“网络不通”,更常见的是:
- 网络可通
- 但延迟高
- 或有抖动
- 或跨机房路径不合理
表现通常是:
- 偶发超时
- RT 波动很大
- 同一接口时好时坏
所以排障时不能只问:
- ping 通不通
还要看:
- 延迟是否稳定
- 是否跨机房
- 是否有丢包/抖动
9. 一套最实用的排障顺序
以后你遇到 Dubbo 问题,我建议优先按这个顺序排:
第一步:先确认现象
先弄清楚是:
- 全量失败
- 部分失败
- 偶发失败
- 全部变慢
- 只有某个 Consumer 慢
- 只有某个 Provider 慢
不要一上来就进入猜测。
第二步:看关键指标
重点看:
- QPS
- RT
- 超时数
- 错误率
- 重试次数
- 线程池活跃数
- Provider 实例数
第三步:缩小范围
判断问题更可能在:
- Consumer 侧
- 网络侧
- Provider 侧
- 下游依赖侧
第四步:结合日志和调用链
例如看:
- 哪个时间点开始变差
- 是否刚好发生扩容/发布/配置变更
- 哪个方法最慢
- 是否出现线程池拒绝、序列化异常、连接异常
第五步:最后再决定怎么调优
是要:
- 调超时
- 改重试
- 减小对象体积
- 扩容实例
- 优化 SQL
- 改线程池参数
- 做限流/降级
这个顺序比“直接调参数”可靠得多。
10. 一个典型案例:接口超时,先看哪
假设现象是:
queryUserProfile接口超时率突然升高
建议先这样查:
- 是所有 Consumer 都超时,还是某几个超时?
- 是所有 Provider 都慢,还是某一台慢?
- 最近有没有发布、扩容、配置变更?
- Provider 线程池有没有积压?
- Provider 下游数据库是不是变慢了?
- Consumer 有没有配置重试,导致整体耗时被放大?
这套问法能帮你很快从“一个表象”拆到“一个具体层次”。
11. 调优时最值得优先做的事
很多人一说调优,就想直接改框架参数。
但实际最有性价比的优化,常常是这几类:
11.1 先减无效数据传输
- 减少不必要字段
- 避免超大 DTO
- 避免层层嵌套返回
11.2 先优化下游慢点
- SQL
- Redis
- 外部 HTTP 接口
- 文件/磁盘 I/O
因为很多 Dubbo 慢,本质不是 Dubbo 慢,而是:
- Dubbo 背后的业务执行慢
11.3 合理设置超时和重试
- 不是越大越安全
- 不是越多越可靠
11.4 观察线程池与并发模型
- 是否积压
- 是否拒绝
- 是否存在长尾任务拖住线程
11.5 监控必须先补齐
如果没有:
- RT
- 错误率
- 超时率
- 实例维度指标
- 调用链路追踪
那所谓调优很容易变成拍脑袋。
12. 最容易踩的坑
12.1 请求慢就一味增大超时
这几乎是最常见误区。
超时调大后,用户可能只是更晚失败,系统资源还会被占更久。
12.2 下游已经抖动,还继续加重试
这会把局部问题放大成系统问题。
12.3 不区分“偶发慢”和“持续慢”
偶发抖动和稳定性故障,排查思路不完全一样。
12.4 只盯 Dubbo,不看业务执行
很多问题最终根因在:
- SQL
- 缓存击穿
- 线程阻塞
- 外部依赖过慢
12.5 没有按实例维度看指标
平均值可能很好看,但其中一台机器已经明显异常。
13. 动手演练建议
练习 1:模拟一个慢 Provider
在 Provider 某个方法里加:
Thread.sleep(3000);观察:
- Consumer RT
- 超时数
- 重试行为
练习 2:模拟线程池积压
让 Provider 执行一个慢任务并并发压测,观察:
- 线程池活跃数
- 请求排队
- 平均 RT 与 TP99
练习 3:模拟大对象传输
让接口返回一个明显偏大的 DTO,观察:
- RT 是否上升
- 序列化与反序列化是否变慢
练习 4:模拟错误调参
故意把:
- 超时调很大
- 重试次数调很多
再观察系统在下游抖动时会不会更差。
14. 自测问题
- 为什么 RPC 超时常常只是表象而不是根因?
- 为什么重试有时会让系统更糟?
- 排查 Dubbo 性能问题时,为什么线程池指标很重要?
- 为什么要关注请求/响应对象的体积?
- 遇到慢调用时,为什么不应该先习惯性调大超时?
15. 这一章你至少要带走什么
如果你看完只记住 5 件事,就记下面这 5 件:
- Dubbo 排障一定要按链路拆,不要只盯报错表象
- 超时常常只是结果,真正问题可能在业务、网络、线程池或重试
- 重试和超时要一起看,否则很容易把问题放大
- 线程池、对象体积、下游依赖,都是 RPC 调优的高频关键点
- 没有监控、日志和调用链,调优大概率会变成猜测
把这套排障思路建立起来,后面你再遇到 Dubbo 调用慢、错误率高、超时飙升,就不会只会“改大超时再试试”了。