RPC 框架

候选人小王面试美团L7岗位,简历上写着"熟悉 Dubbo"。

面试官问:"Dubbo 的负载均衡策略有哪些?"

小王说:"轮询、随机、加权……还有一个一致性哈希。"

面试官追问:"一致性哈希的虚拟节点是什么?为什么需要虚拟节点?"

小王愣了一下:"好像是为了解决数据倾斜问题……"

面试官继续追问:"那 Dubbo 的一致性哈希负载均衡用的是哪个 hash 算法?CRC32、MD5 还是 MurmurHash?"

小王彻底说不出话了。

【面试官心理】

我问他 Dubbo,其实不是在考他知道多少种负载均衡策略。我是想知道他有没有去看过 Dubbo 的源码实现。一致性哈希用的 MurmurHash,这个点没看过源码的根本不知道。

一、内容版图

本模块覆盖 RPC 框架的面试核心点:

目录覆盖内容
rpc-principleRPC 原理、序列化协议、协议栈
dubbo-architectureDubbo 整体架构、Provider/Consumer/Registry/Monitor
dubbo-export服务暴露与引用原理、动态代理
dubbo-loadbalance负载均衡策略:Random/RoundRobin/LeastActive/ConsistentHash
dubbo-fault-tolerance容错机制:Failover/Failfast/Failsafe/Failback
dubbo-generic泛化调用原理、序列化机制
grpcgRPC 原理、ProtoBuf、HTTP/2
grpc-modesgRPC 四种调用模式
rpc-comparisonRPC 框架横向对比:Dubbo vs gRPC vs Feign
zookeeperZookeeper 原理、Watch 机制
zookeeper-zabZAB 协议、Leader 选举
zookeeper-scenariosZookeeper 在 Dubbo 中的应用场景
nacosNacos 注册中心原理
registry-comparison注册中心对比:Zookeeper vs Nacos vs Eureka

二、高频面试题地图

2.1 RPC 原理 🔴

第一层:RPC 是什么?
  → 远程过程调用,像调用本地方法一样调用远程方法
  → 核心:序列化、反序列化、协议栈、网络传输

第二层:一次 RPC 调用的完整流程
  → Consumer 发起调用 → 序列化 → 网络传输 → Provider 反序列化
  → Provider 执行 → 序列化 → 网络传输 → Consumer 反序列化 → 返回结果

第三层:序列化协议对比
  → JDK 序列化:性能差、不支持跨语言
  → Hessian:性能好、但兼容性差
  → Protobuf:性能最优、但需要预定义 schema
  → JSON:可读性好、但性能差
💡

序列化协议的选择是 P6/P7 常问的权衡问题。能说清楚"性能 vs 可读性 vs 跨语言"这个三角关系的,基本都能过这一关。

2.2 Dubbo 架构 🔴

第一层:Dubbo 的四大组件
  → Provider:服务提供者
  → Consumer:服务消费者
  → Registry:注册中心(Zookeeper/Nacos)
  → Monitor:监控中心

第二层:服务注册与发现的过程
  → Provider 启动时向 Registry 注册
  → Consumer 启动时从 Registry 订阅
  → Registry 推送最新的 Provider 列表给 Consumer

第三层:Dubbo 为什么这么快?
  → 相比于 HTTP,Dubbo 协议少了 HTTP header 的开销
  → 使用 Netty 异步通信
  → 单一长连接,减少握手次数
⚠️

很多人说"Dubbo 比 Spring Cloud 快",但说不清楚为什么快。这个问题的本质是协议层和连接管理的差异,不理解这个只能说还在背八股。

【面试官心理】

Dubbo 架构是基础,连这个都说不清楚的可以直接淘汰。但能说清楚架构的人很多,能说出"Dubbo 为什么快"的才是真正理解的。

2.3 服务注册与发现 🟡

第一层:注册中心挂了怎么办?
  → Consumer 本地缓存 Provider 列表
  → 短暂不可用不影响已有连接

第二层:Zookeeper vs Nacos
  → Zookeeper:CP 原则,选举期间不可用
  → Nacos:支持 AP 和 CP 切换

第三层:服务实例上下线时,Consumer 是怎么感知的?
  → Zookeeper:Watch 机制,临时节点
  → Nacos:主动推送 + 定时轮询

2.4 负载均衡策略 🔴

第一层:四种负载均衡策略
  → Random:加权随机
  → RoundRobin:加权轮询,存在数据倾斜问题
  → LeastActive:活跃度加权,响应快的服务器承担更多请求
  → ConsistentHash:一致性哈希,解决数据倾斜

第二层:一致性哈希的原理
  → 哈希环、虚拟节点
  → MurmurHash 算法(不是 CRC32 或 MD5)

第三层:为什么需要虚拟节点?
  → 解决节点分布不均匀导致的负载倾斜
  → 虚拟节点越多,分布越均匀
📖 点击展开 Dubbo 负载均衡源码
// Dubbo ConsistentHashLoadBalance.java
private final Map<Long, TreeMap<Long, Invoker<T>>> virtualInvokers = new HashMap<>();

// 使用 MurmurHash 计算 hash 值
int identityHashCode = System.identityHashCode(invokers);
long hash = MurmurHash.hash64(invokers.get(i).getUrl().toFullString());

一致性哈希用 MurmurHash 而不是 MD5,是因为 MurmurHash 计算更快,且分布更均匀。

2.5 容错机制 🟡

第一层:六种容错策略
  → Failover:失败自动切换(默认)
  → Failfast:快速失败,只调用一次
  → Failsafe:失败安全,静默处理
  → Failback:失败自动恢复,定时重试
  → Forking:并行调用多个,只要一个成功就返回
  → Broadcast:广播调用,所有节点都执行

第二层:什么场景用什么策略?
  → 读操作:Failover
  → 非幂等写操作:Failfast
  → 关键数据写入:Failover + 重试次数限制

第三层:Dubbo 的重试机制
  → retries 配置:重试次数(不含第一次调用)
  → 超时时间:timeout 配置
  → 重试风暴问题

【面试官心理】

容错机制这个问题,我通常用来试探候选人有没有做过高可用架构设计。只知道"失败重试"的是入门水平,能说清楚"重试风暴"和"幂等性保证"的才是有实战经验的。

2.6 泛化调用 🟢

第一层:什么是泛化调用?
  → Consumer 不依赖 Provider 的接口 jar,直接调用
  → 用途:Mock 测试、网关、测试平台

第二层:泛化调用的原理
  → GenericFilter:拦截泛化调用请求
  → PojoSerialize:将请求参数序列化为 POJO
  → P2P 调用:点对点直连

2.7 gRPC 🟢

第一层:gRPC 和 Dubbo 的区别
  → gRPC:基于 HTTP/2,支持双向流
  → Dubbo:基于自定义协议,性能更好

第二层:ProtoBuf 的优势
  → 体积小:是 JSON 的 1/10
  → 速度快:序列化是 JSON 的 100 倍
  → 强类型:schema 校验

第三层:gRPC 的四种调用模式
  → Unary:一元调用(普通 RPC)
  → Server streaming:服务端流
  → Client streaming:客户端流
  → Bidirectional streaming:双向流

三、生产避坑指南

3.1 Dubbo 常见翻车点

场景问题后果解决方案
超时配置不合理timeout 过短慢Provider被频繁超时按业务场景配置分级超时
重试风暴所有消费者同时重试雪崩效应限制重试次数 + 熔断
注册中心选错Zookeeper 选举时不可用服务调用失败核心场景用 Nacos AP 模式
直连Provider硬编码地址失去负载均衡能力仅用于测试环境

3.2 服务超时与重试

// 典型问题:timeout 配置太短 + retries 配置太多
@Reference(timeout = 1000, retries = 3)
private UserService userService;

// 问题:第一次调用失败后会重试3次
// 实际总耗时:可能达到 4 秒(1秒 × 4次)
// 如果业务本身就需要1秒,直接超时

// 正确配置:根据业务耗时合理配置
@Reference(timeout = 3000, retries = 1)
private UserService userService;

3.3 序列化坑点

// 典型问题:使用了 JDK 序列化
// 配置:<dubbo:protocol serialization="jdk" />

// 问题:性能差 + 不支持跨语言 + 安全漏洞

// 正确做法:使用 Hessian 或 Protobuf
<dubbo:protocol serialization="hessian2" />
// 或者
<dubbo:protocol serialization="fastjson" />
📖 点击展开序列化性能对比
序列化方式性能可读性跨语言兼容性
JDK 序列化最差
JSON
Hessian中等中等
Protobuf最优需要 schema

性能差距:JSON 序列化是 Protobuf 的 100 倍慢

四、学习路径指引

4.1 P5 层级:会用 RPC

  • 知道 RPC 的基本概念和作用
  • 会使用 Dubbo 进行服务调用
  • 能配置服务暴露和引用

期望回答:能讲清楚 RPC 调用的大致流程,知道 Provider 和 Consumer 的关系。

4.2 P6 层级:懂原理

  • 能说清楚 Dubbo 的架构和负载均衡策略
  • 知道服务注册与发现的过程
  • 有处理超时、重试、容错配置的经验

期望回答:能回答追问,不怵细节,有生产配置经验。

4.3 P7 层级:能选型、能架构

  • 能根据业务场景做 RPC 框架选型
  • 知道 Dubbo 和 gRPC 的取舍
  • 有服务治理、流量控制、熔断设计经验
  • 了解注册中心的 CAP 权衡

期望回答:有全局视野,能说清楚取舍,能讲清楚一个完整的微服务治理方案。

五、导航指引

💡

RPC 面试的进阶路径:先搞懂"Dubbo 为什么快"和"负载均衡策略",再准备"容错机制和重试"的生产案例,最后能说清楚 Dubbo 和 gRPC 的取舍。做到这三点,P7 面试稳了。