让你设计一个 RPC 框架,怎么设计?
- 标签:后端、系统设计、场景题
🏛 业务架构师
设计 RPC 框架我会按分层拼装思路走,先明确业务定位:公司内部服务调用(治理优先)还是开放生态(兼容优先)——定位决定全部取舍。
核心分层:
- 动态代理(易用性层):消费方拿到接口就像调本地方法——代理把"调用"翻译成网络请求。开发者体验是框架的第一产品力;
- 序列化(成本层):JSON(可读、慢)vs Protobuf/Hessian(快、二进制)——高频内调用二进制,跨语言场景 proto 契约;序列化约占调用耗时的三成,是性价比最高的优化点;
- 协议(通信契约):魔数(快速识别坏包)+ 版本号(演进能力,新旧服务共存的前提)+ 消息类型 + 序列化方式 + body 长度 + body——长度域解决粘包;
- 网络传输(性能层):Netty(Reactor 模型 + 零拷贝)——不裸写 NIO,成熟轮子白拿正确性;
- 注册中心(寻址层):Nacos/ZK 存活的服务提供者列表,变更推送;
- 负载均衡(流量分配层):轮询/加权/一致性哈希(有状态路由:同一用户落同一实例,本地缓存生效);
- 集群容错(可靠性层):failover(重试其他节点)、failfast、failsafe、熔断降级——按方法粒度配置超时与重试,只重试幂等操作;
- 治理(运营层):调用链追踪、QPS/耗时监控、灰度路由(按标签分流——发布安全的抓手)。
演进建议:v1 用 HTTP+JSON 把链路跑通(两周上线),业务量证明瓶颈后再换二进制协议与连接复用——先解决有没有,再解决好不好。别学 Dubbo 全家桶一步到位,框架的复杂度要用业务规模来付费。
一句话:RPC 框架 = 通信管道 + 服务治理,通信 20 分就能用,治理 80 分才好用。
🏗 系统架构师
设计 RPC 框架我从五个核心问题出发搭骨架(本质是把 Dubbo 的核心抽象自己推一遍):
1. 动态代理(调用透明化):客户端拿到接口的动态代理(JDK/CGLIB),helloService.hi("x") 被拦截转为远程调用——本地编码体验、远程执行。
2. 序列化与协议:方法名 + 参数类型 + 参数序列化(选型:Hessian2/Kryo/Protobuf——性能与跨语言权衡);自定义协议:魔数 + 版本 + 消息类型 + 序列化算法 + 长度 + 请求 ID + body,解决 TCP 粘包拆包(Netty LengthFieldBasedFrameDecoder)。请求 ID 支撑异步转同步(Future 与响应配对)。
3. 网络通信:Netty 实现,主从 Reactor 线程模型 + 长连接复用 + 心跳空闲检测;IO 线程与业务线程池隔离。
4. 注册中心与服务发现:服务提供者启动注册(接口 → 实例列表),消费者订阅变更通知(ZK/Nacos/Redis);本地缓存注册表,注册中心故障可降级直连。
5. 集群容错与负载均衡:客户端负载均衡(随机/轮询/一致性哈希/最小活跃调用);容错策略——failover 重试(次数上限)、failfast、failsafe;超时分级(方法级配置)、熔断降级集成。
横切增强:链路追踪(traceId 透传)、监控埋点(QPS/RT 分布)、泛化调用、SPI 扩展点机制(每个环节可替换,参考 Dubbo 的 SPI 设计)。
架构心法:RPC = 代理 + 协议 + 传输 + 治理,先把"一次调用"的路径画出来,每个环节留扩展点,这就是框架设计的分层解耦思维。
💻 全栈开发专家
设计 RPC 框架就是把"远程调用像本地方法"拆成六层,我按调用链讲(对标 Dubbo 分层):
1. 代理层(对用户透明):消费端接口没有实现类——JDK 动态代理把方法调用变成 Invocation(接口名+方法名+参数类型+参数值)。
2. 序列化层:Invocation → 二进制。选 protobuf(体积小、向后兼容),JSON 可调试但慢。
3. 协议层(自定义 TCP 协议,解决粘包):
text
魔数(2B) | 版本(1B) | 序列化方式(1B) | 消息类型(1B) | 请求ID(8B) | body长度(4B) | body长度字段解决 TCP 粘包;请求 ID 关联"请求-响应"(一条连接上异步并发多请求)。
4. 网络传输层:Netty,主从 Reactor,长连接复用 + 心跳(IdleStateHandler)。
5. 注册中心与服务发现:Provider 启动注册(接口→IP:port 元数据);Consumer 订阅变更,本地缓存节点列表。
6. 集群容错层:负载均衡(轮询/一致性 hash/最少活跃)、失败策略(failover 重试别的节点/failfast)、超时控制、熔断限流。
异步转同步的细节(必答题):发起调用后 Future.get(timeout) 挂起,响应回来按请求 ID 找到 Future complete——这就是一条连接支撑并发调用的核心。
扩展设计:SPI 机制让序列化/负载均衡可插拔。最小可跑版本几百行代码,但对齐 Dubbo 的每层职责,面试就非常立体。