Dubbo 的服务调用过程是什么样的?

#中间件#面试八股

Dubbo 的服务调用过程是什么样的?

结论先行:一次调用在消费端要穿过“代理 → 集群 → 目录 → 路由 → 负载均衡 → 过滤器 → 协议 → 交换 → 传输 → 序列化”十道关卡,在提供端按相反顺序还原。真正决定成败的是超时、重试与线程池三处边界。

一、消费端全链路

调用 userService.getById(1L)
  │
  ├─ Proxy          生成 RpcInvocation(方法名、参数类型、实参、attachment)
  ├─ Cluster        FailoverClusterInvoker:循环重试
  ├─ Directory      拿到该接口的 invoker 列表(本地缓存,注册中心变更时刷新)
  ├─ Router         按条件/标签规则过滤
  ├─ LoadBalance    选出一个 invoker(默认 random 加权)
  ├─ Filter         消费端过滤器链:超时、埋点、限流、泛化
  ├─ Protocol       组装 Request
  ├─ Exchange       分配 requestId,建 DefaultFuture
  ├─ Transport      Netty 写出
  └─ Serialize      Hessian2 编码 body

二、每一层的关键动作

层关键动作出问题时的现象
Proxy生成 RpcInvocation参数类型不匹配、泛化调用失败
Cluster选择容错策略,组织重试重试放大流量、非幂等重复执行
Directory维护 invoker 列表与本地缓存地址为空、旧地址未摘除
Router过滤候选集灰度流量打错、跨机房调用
LoadBalance加权选择单机热点、长尾延迟
Filter埋点、鉴权、限流链路断点、限流误杀
Protocol/Exchange编码、requestId 配对响应错配、连接断开
Transport连接复用与心跳连接数暴涨、假死

三、提供端反向流程

Netty 读到字节
  → 解码(长度字段拆包)
  → 反序列化得到 Invocation
  → 查 Exporter(按 interface + version + group 定位)
  → 过滤器链(Provider 侧)
  → 反射调用实现类
  → 结果序列化写回,附 requestId

请求 ID 是消费端与提供端唯一的对应关系,一条连接上并发多个请求时靠它把响应唤醒到正确的 Future。

四、可调的关键参数

dubbo:
  consumer:
    timeout: 1000        # 单次调用超时,毫秒
    retries: 2           # 失败重试次数,仅幂等方法开启
    check: false         # 启动时不检查提供者是否存在
    actives: 100         # 每服务每方法最大并发
  provider:
    threads: 200         # 业务线程池大小
    executes: 200        # 每方法最大并发执行数
    accepts: 0           # 允许的最大连接数,0 表示不限制
// 单方法覆盖全局配置
@DubboReference(timeout = 500, retries = 0, cluster = "failfast")
private PayService payService;

常见追问

  • 追问:超时时间从哪一刻开始算?从发起调用算起,包含排队、路由、网络、服务端处理全过程;消费端超时后不保证服务端停止执行,所以写操作必须靠幂等兜底。
  • 追问:重试为什么容易引发雪崩?一次失败变成三次请求,下游已过载时流量被放大三倍,所以非幂等接口要把 retries 设为 0。
  • 追问:actives 和 executes 有什么区别?前者限制消费端对某服务的并发请求数,后者限制提供端某方法的并发执行数,一个在入口一个在出口。

小结

  • 消费端十层串联,提供端反向还原,requestId 是并发下的配对依据。
  • 路由决定“能不能去”,负载均衡决定“去哪个”,重试决定“失败再去哪”。
  • 超时是端到端的,消费端放弃不代表服务端停止。
  • 非幂等接口必须关重试,否则故障会被放大。
笔记加载中…