Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⭐详细开发文档:https://www.yuque.com/chenhaopeng0222/hafeao

手写 RPC 框架

一个基于 Java 实现、功能完备的轻量级 RPC 框架。


项目简介

本项目是一个从零开始手写 RPC(Remote Procedure Call,远程过程调用)框架的实践。它模拟了主流 RPC 框架(如 Dubbo、gRPC)的核心能力,支持自定义网络协议、多种序列化、注册中心、负载均衡、容错重试以及 SPI 扩展机制,并提供 Spring Boot Starter,让使用像调用本地方法一样简单。 image

核心特性

  • 自定义网络协议:设计魔数、消息头、消息体结构,解决粘包/半包问题
  • 多种序列化:支持 JDK、Hessian、JSON、Kryo,基于 SPI 可自由扩展
  • 注册中心:支持 Etcd、ZooKeeper,内置服务发现、心跳续期、节点监听、本地缓存
  • 负载均衡:支持轮询、随机、一致性哈希三种策略
  • 容错机制:内置重试策略(不重试 / 固定间隔重试)与容错策略(FailFast / FailOver / FailBack / FailSafe)
  • 动态代理:基于 JDK 动态代理实现透明化远程调用,支持 Mock 调用
  • 高性能通信:基于 Vert.x 提供异步 TCP / HTTP 服务端与客户端
  • SPI 扩展机制:自定义实现类 Java SPI,支持键值对映射与用户自定义扩展
  • Spring Boot Starter:通过 @EnableRpc、@RpcService、@RpcReference 注解实现开箱即用

系统架构

模块组成

项目共包含六个 Maven 模块,各司其职:

  1. common(公共模块):存放服务接口与数据模型,供服务提供者和消费者共享依赖。
  2. easy-rpc(简易版框架):项目的初版实现,基于 HTTP 与 JDK 序列化完成最简单的 RPC 调用,用于理解 RPC 基本原理,后续逐步演进。
  3. rpc-core(核心框架):框架的核心实现,包含自定义协议、序列化器、注册中心、负载均衡、容错机制、动态代理、网络通信等全部核心能力。
  4. rpc-springbootstarter(Spring Boot 启动器):基于 Spring Boot 的注解驱动封装,通过 BeanPostProcessor 与 ImportBeanDefinitionRegistrar 将 RPC 能力集成进 Spring 容器。
  5. provider(服务提供者示例):演示如何注册服务并启动服务端。
  6. consumer(服务消费者示例):演示如何通过代理对象发起远程调用。

RPC 调用流程

一次完整的 RPC 调用过程如下:

首先,服务消费者通过 ServiceProxyFactory 获取某个服务接口的代理对象。当业务代码调用代理对象的方法时,代理对象(ServiceProxy)会拦截这次调用,将调用的服务名、方法名、参数类型、参数值封装成一个 RpcRequest 请求对象。

随后,消费者从注册中心获取该服务的所有可用提供者节点列表。拿到节点列表后,通过负载均衡器从中选取一个目标节点。接着,将请求对象序列化后按照自定义协议编码,通过 TCP 发送给目标服务提供者。

服务提供者接收到请求后,先按照协议解码、反序列化还原出 RpcRequest,再根据服务名从本地注册表中查找对应的实现类,通过反射机制调用目标方法,拿到返回结果后封装为 RpcResponse。最后将响应序列化、协议编码后回写给消费者。

消费者接收到响应后解码还原出 RpcResponse,期间如果调用出现异常,会依次触发重试策略和容错策略来保障可用性。最终将响应数据返回给业务调用方,整个远程调用过程对开发者透明,就像调用本地方法一样。


核心模块详解

1. rpc-core(核心框架)

协议设计(protocol)

框架采用自定义二进制协议进行网络传输,每条消息由消息头(Header)和消息体(Body)两部分组成。消息头固定长度为 17 字节,包含以下字段:

  • magic(魔数):值为 0x1,用于校验报文合法性,拒绝非法请求。
  • version(版本号):协议版本,便于后续协议升级兼容。
  • serializer(序列化器):标识消息体使用的序列化方式。
  • type(消息类型):区分请求、响应、心跳等不同消息类型。
  • status(状态码):表示响应状态(如成功、失败)。
  • requestId(请求 ID):全局唯一标识一次请求,便于链路追踪。
  • bodyLength(消息体长度):记录消息体的字节长度,用于解决粘包/半包问题。

消息体则是经过序列化后的请求或响应对象。ProtocolMessageEncoder 负责将协议消息编码为字节流,ProtocolMessageDecoder 负责将字节流解码还原为协议消息。为了解决 TCP 通信中的粘包和半包问题,框架通过 Vert.x 的 RecordParser 配合 TcpBufferHandlerWrapper(装饰者模式)实现按消息头中记录的长度精准读取消息体。

序列化器(serializer)

序列化器负责将 Java 对象与字节序列之间相互转换。框架基于 SPI 机制实现了多种序列化方式,可通过配置自由切换:

  • JdkSerializer(jdk):基于 Java 原生序列化,兼容性好但性能一般、体积较大,为默认实现。
  • HessianSerializer(hessian):基于 Hessian 协议,跨语言支持好,序列化体积小、性能优秀。
  • JsonSerializer(json):基于 Jackson 库,序列化结果可读性强,便于调试,但体积较大。
  • KryoSerializer(kryo):基于 Kryo 框架,高性能、紧凑,适合对性能要求高的场景。

注册中心(registry)

注册中心负责服务地址的注册与发现,是 RPC 框架的核心组件之一。框架统一抽象出 Registry 接口,定义了初始化、注册、注销、服务发现、心跳检查、节点监听、销毁等能力:

  • EtcdRegistry:基于 Etcd 实现。利用 Etcd 的租约(Lease)机制实现服务注册,每个服务节点注册时创建一个 30 秒的租约。同时通过定时任务(每 10 秒执行一次)对所有已注册节点进行续期,保证服务在线。消费端通过前缀查询获取服务列表,并利用 Etcd 的 Watch 机制监听节点变化,当节点被删除时自动清除本地服务缓存。
  • ZooKeeperRegistry:基于 Apache Curator 实现,利用 ZooKeeper 的临时节点特性(会话失效自动删除)实现服务注册与下线,同样支持节点监听。
  • LocalRegistry:本地注册表,维护服务名到实现类的映射,供服务端通过反射调用具体实现。
  • RegistryServiceCache:消费端的服务发现缓存,避免每次调用都访问注册中心,减轻注册中心压力,配合 Watch 监听实现缓存的自动失效与更新。

负载均衡(loadbalancer)

当同一个服务存在多个提供者节点时,负载均衡器负责决定本次调用选择哪个节点。框架内置三种策略:

  • RoundRobinLoadBalancer(roundRobin,轮询):默认策略,按顺序依次轮流选择节点,保证每个节点被调用的次数基本均等。
  • RandomLoadBalancer(random,随机):从可用节点列表中随机选取一个,实现简单,在节点数量较多时分布较为均匀。
  • ConsistentHashLoadBalancer(consistentHash,一致性哈希):基于一致性哈希算法,为每个真实节点生成 100 个虚拟节点并构建哈希环。调用时根据请求参数的哈希值在环上顺时针查找最近的节点,适合需要将相同参数的请求固定路由到同一节点的场景。

容错机制(fault)

容错机制用于在远程调用失败时保障系统的可用性,分为重试策略和容错策略两个层面。

重试策略(retry) 在单次调用失败时自动重试:

  • NoRetryStrategy(no):默认策略,不进行重试,直接抛出异常。
  • FixedIntervalRetryStrategy(fixedInterval):固定间隔重试策略,基于 guava-retrying 实现,在固定时间间隔后重试,达到最大重试次数后放弃。

容错策略(tolerant) 在重试仍然失败后作为兜底处理:

  • FailFastTolerantStrategy(failFast):默认策略,快速失败,直接记录并抛出异常,让调用方感知问题。
  • FailSafeTolerantStrategy(failSafe):安全失败,忽略异常并返回默认结果,适用于日志记录等非核心调用。
  • FailOverTolerantStrategy(failOver):故障转移,自动尝试切换到其他服务节点重新发起调用。
  • FailBackTolerantStrategy(failBack):失败回退,对失败请求进行降级处理或异步重试补偿。

服务代理(proxy)

服务代理是实现"像调用本地方法一样调用远程服务"的关键,基于 JDK 动态代理实现:

  • ServiceProxy:核心代理类,实现了 InvocationHandler 接口。当代理对象的方法被调用时,它会依次完成:构造 RpcRequest、从注册中心发现服务、负载均衡选择节点、通过 TCP 发起 RPC 调用、触发重试与容错机制、返回最终结果。
  • ServiceProxyFactory:代理工厂,根据全局配置决定返回真实的 ServiceProxy 还是 MockServiceProxy。当配置开启 Mock 模式时,返回 Mock 代理,便于在缺少服务提供者的环境下独立开发和调试。
  • MockServiceProxy:Mock 代理,对任何方法调用都返回对应类型的默认值(如 null、0、false 等)。

服务器(server)

服务器模块负责网络通信,基于 Vert.x 框架实现异步高性能通信:

  • VertxTcpServer / VertxTcpClient:基于 Vert.x NetServer 和 NetClient 实现的 TCP 服务端与客户端,使用自定义二进制协议进行通信,是框架的主要通信方式。服务端通过 TcpServerHandler 处理请求,客户端(VertxTcpClient)发送请求并接收响应。
  • VertxHttpServer:基于 Vert.x HttpServer 实现的 HTTP 服务端,是框架早期的通信方式,通过 HttpServerHandler 处理请求。目前主要通信已迁移到 TCP。
  • HttpServerHandler / TcpServerHandler:请求处理器,负责解码协议消息、反序列化请求、从本地注册表查找实现类并通过反射调用方法、封装响应结果并编码返回。

2. rpc-springbootstarter(Spring Boot 集成)

为了让 RPC 框架更易使用,项目提供了 Spring Boot Starter,通过注解驱动的方式将 RPC 能力无缝集成进 Spring 应用。核心借助 Spring 的 BeanPostProcessor(Bean 后置处理器)与 ImportBeanDefinitionRegistrar(Bean 定义注册器)实现:

  • @EnableRpc:标注在启动类上,用于启动 RPC 框架。通过 needServer 属性控制是否启动服务端(服务提供者需要启动,纯消费者可不启动)。它通过 @Import 导入三个 Bootstrap 类,在 Spring 容器初始化时完成框架初始化、服务注册、服务注入等操作。
  • @RpcService:标注在服务提供者的实现类上,框架在 Bean 初始化后会自动检测该注解,将服务注册到本地注册表和注册中心,完成服务发布。
  • @RpcReference:标注在消费者的字段上,框架在 Bean 初始化后会扫描该注解,为字段生成并注入服务代理对象,开发者直接使用即可发起远程调用。

快速开始

环境要求

  • JDK 11 及以上版本
  • Maven 3.6 及以上版本
  • Etcd 或 ZooKeeper 注册中心(默认使用 Etcd)

使用步骤

第一步:启动注册中心。 以 Etcd 为例,启动后默认监听地址 http://localhost:2380,框架会通过该地址连接注册中心。

第二步:编写公共接口(common 模块)。 在 common 模块中定义服务接口(如 UserService),该接口会被服务提供者和消费者共同依赖。接口中定义业务方法,例如 getUser 方法接收一个 User 对象并返回处理后的 User 对象。

第三步:编写服务提供者(provider 模块)。 首先实现 common 模块中定义的服务接口(如 UserServiceImpl)。然后通过 ProviderBootstrap 的 init 方法初始化服务提供者,传入要注册的服务信息列表(包含服务接口和实现类)。初始化过程中会自动完成 RPC 框架启动、服务本地注册、服务注册到注册中心、启动 TCP 服务器等操作。

第四步:编写服务消费者(consumer 模块)。 首先通过 ConsumerBootstrap 的 init 方法初始化消费者(完成框架配置加载和注册中心连接)。然后通过 ServiceProxyFactory 的 getProxy 方法获取服务接口的代理对象。拿到代理对象后,即可像调用本地方法一样发起远程调用,所有网络通信、序列化、服务发现、负载均衡等细节均由框架自动处理。

第五步:Spring Boot 集成方式。 若使用 Spring Boot,只需在启动类上添加 @EnableRpc 注解即可启动框架。服务提供者在实现类上添加 @RpcService 注解即可自动注册服务;消费者在需要注入服务的字段上添加 @RpcReference 注解即可自动获得代理对象。无需任何手动初始化代码,真正实现开箱即用。


配置说明

框架通过 application.properties 配置文件(也支持按环境区分的 application-{env}.properties)进行配置,所有配置项以 rpc 为前缀。主要配置项说明如下:

框架基础配置:

  • rpc.name:框架名称,默认为 rpc。
  • rpc.version:框架版本号,默认为 1.0。
  • rpc.serverHost:服务端主机地址,默认为 localhost。
  • rpc.serverPost:服务端监听端口,默认为 8080。
  • rpc.mock:是否开启 Mock 调用模式,默认为 false。开启后所有服务调用返回默认值,便于独立调试。

核心组件配置:

  • rpc.serializer:序列化器类型,可选值为 jdk、hessian、json、kryo,默认为 jdk。
  • rpc.loadBalancer:负载均衡器类型,可选值为 roundRobin(轮询)、random(随机)、consistentHash(一致性哈希),默认为 roundRobin。
  • rpc.retryStrategy:重试策略,可选值为 no(不重试)、fixedInterval(固定间隔重试),默认为 no。
  • rpc.tolerantStrategy:容错策略,可选值为 failFast(快速失败)、failSafe(安全失败)、failOver(故障转移)、failBack(失败回退),默认为 failFast。

注册中心配置(rpc.registryConfig 前缀):

  • rpc.registryConfig.registry:注册中心类型,可选值为 etcd、zookeeper,默认为 etcd。
  • rpc.registryConfig.address:注册中心地址,默认为 http://localhost:2380。
  • rpc.registryConfig.timeout:注册中心连接超时时间(毫秒),默认为 10000。
  • rpc.registryConfig.username / rpc.registryConfig.password:注册中心的用户名和密码(如需认证)。

SPI 扩展机制

框架采用自定义 SPI(SpiLoader)机制实现组件的可插拔扩展,相比 Java 原生 SPI,支持键值对映射(每个实现对应一个 key),并内置实例缓存(单例模式,避免重复实例化)。

SPI 配置文件存放在两个目录下:

  • 系统级 SPI 目录(META-INF/rpc/system/):存放框架内置的实现,如各种序列化器、注册中心、负载均衡器、重试策略、容错策略等。
  • 用户自定义 SPI 目录(META-INF/rpc/custom/):存放用户自行扩展的实现,优先级高于系统级 SPI,可以覆盖默认实现。

扩展步骤: 以新增一个序列化器为例,首先实现 Serializer 接口并编写具体的序列化与反序列化逻辑;然后在 META-INF/rpc/custom/ 目录下创建以接口全限定名为文件名的配置文件(如 com.chp.rpc.serializer.Serializer),在文件中按照"key=实现类全限定名"的格式添加一行配置;最后在 application.properties 中将 rpc.serializer 配置为你自定义的 key 即可生效。

当前框架已支持的 SPI 扩展点包括:Serializer(序列化器)、Registry(注册中心)、LoadBalancer(负载均衡器)、RetryStrategy(重试策略)、TolerantStrategy(容错策略)。开发者可以针对任何一个扩展点实现自定义逻辑,无需修改框架源码。


技术栈

  • 开发语言:Java 11
  • 构建工具:Maven
  • 网络通信:Vert.x(异步事件驱动的 TCP / HTTP 通信框架)
  • 注册中心:Etcd(通过 jetcd 客户端接入)、ZooKeeper(通过 Apache Curator 接入)
  • 序列化:JDK 原生序列化、Hessian、Jackson(JSON)、Kryo
  • 重试机制:guava-retrying
  • 工具库:Hutool(通用工具集)、Lombok(简化样板代码)
  • 框架集成:Spring Boot 3.x

项目结构

rpc-core 作为核心框架,其源码主要目录组织如下:

  • RpcApplication.java:框架全局入口类,采用双检锁单例模式持有全局配置,提供框架初始化与配置获取能力。
  • bootstrap:启动类目录,包含 ProviderBootstrap(服务提供者初始化)和 ConsumerBootstrap(服务消费者初始化)。
  • config:配置类目录,包含 RpcConfig(框架全局配置)和 RegistryConfig(注册中心配置)。
  • constant:常量定义目录。
  • fault:容错机制目录,下设 retry(重试策略)和 tolerant(容错策略)两个子包。
  • loadbalancer:负载均衡目录,包含接口定义、三种实现及工厂类。
  • model:数据模型目录,包含 RpcRequest、RpcResponse、ServiceMetaInfo、ServiceRegisterInfo 等。
  • protocol:自定义协议目录,包含协议常量、协议消息结构、编解码器及各类枚举。
  • proxy:动态代理目录,包含服务代理、Mock 代理及代理工厂。
  • registry:注册中心目录,包含接口定义、Etcd 实现、ZooKeeper 实现、本地注册表、服务缓存及工厂类。
  • serializer:序列化器目录,包含接口定义、四种实现、工厂类及自定义 SPI 加载器(spi 子包)。
  • server:服务器目录,包含 HTTP 与 TCP 两种通信方式的服务端、客户端及请求处理器(TCP 相关在 tcp 子包中)。
  • utils:工具类目录,包含配置加载工具类 ConfigUtils。

  1. 初版 RPC(easy-rpc 模块):从最简单的实现入手,理解 RPC 的基本原理 —— 通过 HTTP 传输、JDK 序列化、动态代理完成一次远程调用。
  2. 自定义协议:学习如何设计网络协议结构(魔数、消息头、消息体),理解并解决 TCP 粘包/半包问题,将通信方式从 HTTP 迁移到更高性能的 TCP。
  3. 序列化扩展:理解 SPI 机制原理,实现自定义 SPI 加载器,并基于此扩展 Hessian、JSON、Kryo 等多种序列化方式。
  4. 注册中心:从本地注册表演进到分布式注册中心(Etcd / ZooKeeper),学习服务注册与发现、心跳续期、节点监听、本地缓存等机制。
  5. 负载均衡:理解多节点场景下的流量分配问题,实现轮询、随机、一致性哈希等负载均衡策略。
  6. 容错机制:学习重试策略与容错策略的设计,理解 FailFast、FailOver、FailBack、FailSafe 等经典容错模式的区别与适用场景。
  7. Spring Boot Starter:学习如何将框架封装为 Spring Boot Starter,通过注解驱动和 Spring 容器扩展点实现优雅集成。

所有权声明

本项目(Hand-written RPC framework - 手写 RPC 框架)由 chp 独立设计并开发,相关知识产权归作者所有。

  • 本项目仅用于 学术交流 目的,不得用于任何商业用途。
  • 未经作者书面许可,禁止将本项目代码、设计文档、论文内容等用于商业出版、售卖或二次分发。
  • 引用本项目代码或设计思想时,请注明出处并保留原作者信息。
  • 作者保留对本项目的最终解释权。

About

从零开始手搓 RPC 框架

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages