服务治理 列表全部文章
服务治理
技术笔记›服务治理第 2 / 2 篇

Dubbo 笔记:服务暴露与引用、负载均衡、容错与超时治理

Dubbo 的实战笔记:架构角色与调用链路,服务暴露/引用的配置方式,负载均衡与集群容错策略的选型,超时与重试的正确配置(幂等是关键前提),泛化调用与灰度,以及一次服务超时引发连锁故障的复盘。

更多

Dubbo 是国内 Java 体系里用得最多的 RPC 框架。它的能力很全,但默认配置偏乐观(比如重试),直接用容易出事。

一、架构与调用链路

Provider 服务提供者 Registry 注册中心 Consumer 服务消费者 Monitor Container 1. Provider 启动 → 向 Registry 注册服务(register) 2. Consumer 启动 → 订阅服务(subscribe),拿到 Provider 列表并缓存到本地 3. Consumer 直连 Provider 发起 RPC(不经过注册中心);注册中心挂了只影响新服务发现

关键点:Dubbo 是直连调用,注册中心只做服务发现,不在调用链路上。所以注册中心短暂不可用不会影响已有调用 —— 这是它比早期 Spring Cloud(Eureka + Ribbon 也类似)之外很多方案更稳的地方。

二、基本配置

dubbo:
  application:
    name: order-service
  registry:
    address: nacos://10.0.0.10:8848
    # 或用 zookeeper://10.0.0.10:2181
  protocol:
    name: dubbo
    port: 20880
    serialization: hessian2        # 也支持 protobuf / kryo
    threads: 200                   # 业务线程池大小
  provider:
    timeout: 3000                  # ★ 服务端超时
    retries: 0                     # ★ 服务端默认重试次数,建议 0
    filter: -exception             # 按需
  consumer:
    timeout: 3000
    retries: 0                     # ★ 关键:默认 2 次重试,非幂等接口会出事
    check: false                   # 启动时不检查提供者是否可用(避免启动顺序死锁)
    loadbalance: leastactive

服务暴露与引用:

@DubboService(version = "1.0.0", group = "prod", timeout = 3000)
public class OrderServiceImpl implements OrderService { ... }

@DubboReference(version = "1.0.0", group = "prod", check = false)
private OrderService orderService;

version 和 group 是做灰度与多版本共存的关键:新老版本同时注册,消费者按 version 引用,就能平滑升级。

三、负载均衡策略

策略 说明 场景
random(默认) 随机,按权重 通用,简单
roundrobin 轮询 各节点性能接近
leastactive 最少活跃调用 推荐:慢的节点自动少分请求
consistenthash 一致性哈希 需要同一参数落到同一节点(如有状态场景)
shortestresponse 最短响应(2.7.7+) 响应时间差异大时

我一般选 leastactive,它能自动规避慢节点,比 random 稳。

四、集群容错

策略 行为 场景
failover(默认) 失败自动切换其它节点重试 幂等的读操作
failfast 失败立即报错 非幂等的写操作(下单、扣款)
failsafe 失败忽略 日志、通知这类可丢的
failback 失败记录,定时重发 消息通知
forking 并行调用多个,取第一个成功 实时性要求高,浪费资源
broadcast 广播所有节点 通知所有节点刷新缓存

最重要的一条:写接口必须配 failfast + retries=0。Dubbo 默认 failover + 2 次重试,一个下单接口超时后自动重试 2 次,就是 3 个订单。

@DubboReference(cluster = "failfast", retries = 0, timeout = 2000)
private PayService payService;

五、超时与重试的正确配置

Dubbo 的超时有几层,优先级:方法级 > 接口级 > 全局(consumer > provider)。

@DubboService(methods = {
    @Method(name = "batchCreate", timeout = 10000),   // 批量方法单独放宽
    @Method(name = "queryOne",    timeout = 1000)
})

配置原则:

  1. 消费端优先于服务端:消费端更清楚自己能等多久。
  2. 超时值要大于服务端 P99:否则正常请求也被判超时。
  3. 重试只在幂等接口上开,且次数 ≤ 2。
  4. 超时不是越短越好:设太短会在下游抖动时大量超时,反而放大故障。一般取 P99 的 1.5–2 倍。

六、服务治理常用能力

// 泛化调用(不依赖服务方 JAR,做网关/测试平台很有用)
GenericService genericService = (GenericService) applicationContext.getBean("genericService");
Object result = genericService.$invoke("createOrder",
        new String[]{"com.xxx.OrderDTO"}, new Object[]{paramMap});

// 本地存根(参数校验放消费端,减少网络往返)
@DubboReference(stub = "com.xxx.OrderServiceStub")

// 回声测试(检查服务是否可用)
// telnet 到 dubbo 端口
telnet 10.0.0.10 20880
> ls                    # 列出服务
> invoke com.xxx.OrderService.queryOne(1)

灰度发布:用 version + 路由规则,或者在 Dubbo Admin 里配置标签路由(给部分机器打标,指定流量过去)。

七、序列化选择

方式 性能 跨语言 建议
hessian2(默认) 中 有限 够用
kryo / fst 高 否 纯 Java 内部服务可选
protobuf 高 是 跨语言/长期演进首选
fastjson2 中 是 需要可读性好时

注意:序列化方式服务端和消费端必须一致,否则反序列化失败。生产上统一在 protocol.serialization 里指定,不要各配各的。

八、生产实践

案例:一次超时引发的连锁故障

背景:某 B2B 电商公司的分布式微服务(Dubbo + Zookeeper),订单服务调用库存服务。

现象:大促开始后订单服务大面积超时,最后整个下单链路不可用,但库存服务本身 CPU 正常。

排查:

  1. 看 Dubbo 线程池监控:ThreadPool exhausted,业务线程池(默认 200)全部占满。
  2. jstack 看线程在等什么:都在等库存服务的响应。
  3. 库存服务的日志:有慢 SQL,RT 从 20ms 涨到 2s。
  4. 再看消费端配置:retries=2(默认)—— 库存慢 → 订单超时 → 自动重试 2 次 → 请求量翻 3 倍 → 库存更慢 → 恶性循环。

根因:重试放大了故障。下游已经不堪重负,上游还在给它 3 倍流量。

处理:

  • 把写接口的 retries 改成 0,集群容错改 failfast。
  • 给 Dubbo 线程池加监控和告警(活跃线程数 > 80% 告警)。
  • 修复库存慢 SQL(加索引 + 缓存热点 SKU)。
  • 引入熔断(Sentinel):库存服务 RT 超过阈值时快速失败,不再重试。
  • 线程池参数调优:核心 200 → 400,队列从无界改成 1000(防止内存堆积)。

结论:RPC 框架的重试是双刃剑。读接口重试能提升成功率,写接口重试会造成重复和放大。区分读写、区分幂等,是配置 RPC 的第一原则。

案例:注册中心地址写错导致服务找不到

现象:测试环境服务注册成功,消费端报 No provider available。

排查:ls 命令看不到服务 → 检查 @DubboService 的包路径是否在 @EnableDubbo(scanBasePackages=...) 扫描范围内 → 发现 Provider 的 group 是 test,Consumer 写的是 prod。

教训:group / version 不一致是「明明注册了却找不到」的第一嫌疑。排查顺序:注册中心里有没有 → group/version 是否一致 → 接口全限定名是否一致 → 序列化是否一致。

九、排查命令清单

# Dubbo 的 telnet 运维(连 Provider 的 dubbo 端口)
telnet 127.0.0.1 20880
ls                    # 服务列表
ls -l                 # 详细(含地址)
ps                    # 端口与连接
invoke <接口>.<方法>(参数)   # 直接调用,验证逻辑
count <接口>          # 调用统计
# 日志里开 access log(记录每次调用耗时)
dubbo.protocol.accesslog=true
— 感谢读到这里 —发现错误,或想补充?告诉我 →
TURN KNOWLEDGE INTO SOMETHING REAL

有想法,也想把它做出来?

从技术方案到上线交付,聊聊你的下一个项目。

聊聊需求

一起聊聊

搜索文章

输入关键词,按标题和摘要即时筛选。

浏览分类文章归档

阅读设置

字号
行宽
专注模式

专注模式会隐藏页头与侧栏,只留正文。设置保存在当前浏览器。

稍后阅读

保存在当前浏览器,留给下一次安静阅读。

总浏览 -- 次 · 总访客 -- 人 · 今日浏览 -- 次 · 今日访客 -- 人