A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程。)
See the code虽说 RPC 的原理实际不难,但是,自己在实现的过程中自己也遇到了很多问题。guide-rpc-framework 目前只实现了 RPC 框架最基本的功能,一些可优化点都在下面提到了,有兴趣的小伙伴可以自行完善。
通过这个简易的轮子,你可以学到 RPC 的底层原理和原理以及各种 Java 编码实践的运用。
你甚至可以把 guide-rpc-framework 当做你的毕设/项目经验的选择,这是非常不错!对比其他求职者的项目经验都是各种系统,造轮子肯定是更加能赢得面试官的青睐。
如果你要将 guide-rpc-framework 当做你的毕设/项目经验的话,我希望你一定要搞懂,而不是直接复制粘贴我的思想。你可以 fork 我的项目,然后进行优化。如果你觉得的优化是有价值的话,你可以提交 PR 给我,我会尽快处理。
guide-rpc-framework 是一款基于 Netty、Zookeeper 和可插拔序列化组件实现的 RPC 框架。代码注释详细,结构清晰,非常适合阅读和学习。
CompletableFuture<T>RpcStatusCode、requestId 和类型化异常区分业务、超时、取消、传输与协议失败guide-rpc-framework/
├── rpc-framework-simple/ # RPC 框架核心实现
├── rpc-framework-common/ # 公共工具类和常量
├── hello-service-api/ # 示例服务接口定义
├── example-server/ # 服务提供者示例
├── example-client/ # 服务消费者示例
├── docs/ # 相关文档
└── images/ # 项目图片资源
由于 Guide 哥自身精力和能力有限,如果大家觉得有需要改进和完善的地方的话,欢迎 fork 本项目,然后 clone 到本地,在本地修改后提交 PR 给我,我会在第一时间 Review 你的代码。
我们先从一个基本的 RPC 框架设计思路说起!
注意 :我们这里说的 RPC 框架指的是:可以让客户端直接调用服务端方法就像调用本地方法一样简单的框架,比如我前面介绍的 Dubbo、Motan、gRPC 这些。 如果需要和 HTTP 协议打交道,解析和封装 HTTP 请求和响应。这类框架并不能算是“RPC 框架”,比如 Feign。
一个最简单的 RPC 框架使用示意图如下图所示,这也是 guide-rpc-framework 目前的架构 :

服务提供端 Server 向注册中心注册服务,服务消费者 Client 通过注册中心拿到服务相关信息,然后再通过网络请求服务提供端 Server。
作为 RPC 框架领域的佼佼者Dubbo的架构如下图所示,和我们上面画的大体也是差不多的。
一般情况下, RPC 框架不仅要提供服务发现功能,还要提供负载均衡、容错等功能,这样的 RPC 框架才算真正合格的。
简单说一下设计一个最基本的 RPC 框架的思路:

为了循序渐进,最初的是时候,我是基于传统的 BIO 的方式 Socket 进行网络传输,然后利用 JDK 自带的序列化机制 来实现这个 RPC 框架的。后面,我对原始版本进行了优化,已完成的优化点和可以完成的优化点我都列在了下面 👇。
为什么要把可优化点列出来? 主要是想给哪些希望优化这个 RPC 框架的小伙伴一点思路。欢迎大家 fork 本仓库,然后自己进行优化。
CompletableFuture 包装接收客户端返回结果(之前的实现是通过 AttributeMap 绑定到 Channel 上实现的) 详见:使用 CompletableFuture 实现真正的异步 RPC 调用RpcRequest和 RpcRequest 对象作为消息体,然后增加如下字段(可以参考:《Netty 入门实战小册》和 Dubbo 框架对这块的设计):
首先需要启动 Zookeeper 作为注册中心:
# 拉取 zookeeper 镜像
docker pull zookeeper:3.9.5
# 使用 Docker 启动 Zookeeper
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.9.5
git clone https://github.com/Snailclimb/guide-rpc-framework.git
cd guide-rpc-framework
mvn clean install
在 hello-service-api 模块中定义服务接口和数据传输对象:
// HelloService.java - 服务接口
public interface HelloService {
String hello(Hello hello);
}
// Hello.java - 数据传输对象
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@ToString
public class Hello implements Serializable {
private String message;
private String description;
}
在 example-server 模块中实现服务接口,使用 @RpcService 注解标记服务:
@Slf4j
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {
static {
System.out.println("HelloServiceImpl被创建");
}
@Override
public String hello(Hello hello) {
log.info("HelloServiceImpl收到: {}.", hello.getMessage());
String result = "Hello description is " + hello.getDescription();
log.info("HelloServiceImpl返回: {}.", result);
return result;
}
}
使用 @RpcScan 注解扫描服务,启动 Netty 服务器:
@RpcScan(basePackage = {"github.javaguide"})
public class NettyServerMain {
public static void main(String[] args) {
autoRegistry();
}
public static void autoRegistry() {
try (AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(NettyServerMain.class)) {
NettyRpcServer nettyRpcServer = applicationContext.getBean(NettyRpcServer.class);
HelloService helloService = applicationContext.getBean(HelloServiceImpl.class);
helloService.hello(new Hello("你好fzk", "你好服务端"));
nettyRpcServer.start();
}
}
}
使用 @RpcReference 注解注入远程服务:
@Component
public class HelloController {
@RpcReference(version = "version1", group = "test1")
private HelloService helloService;
public void test() throws InterruptedException {
String hello = this.helloService.hello(new Hello("111", "222"));
//如需使用 assert 断言,需要在 VM options 添加参数:-ea
assert "Hello description is 222".equals(hello);
Thread.sleep(12000);
for (int i = 0; i < 10; i++) {
System.out.println(helloService.hello(new Hello("111", "222")));
}
}
}
@RpcScan(basePackage = {"github.javaguide"})
public class NettyClientMain {
public static void main(String[] args) throws InterruptedException {
try (AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(NettyClientMain.class)) {
HelloController helloController = applicationContext.getBean(HelloController.class);
helloController.test();
}
}
}
127.0.0.1:2181 运行NettyServerMain.main() 方法NettyClientMain.main() 方法@RpcService:标记服务提供者,支持 group 和 version 属性用于服务分组和版本控制@RpcReference:标记服务消费者,通过 group 和 version 属性指定要调用的服务@RpcScan:指定要扫描的包路径,自动注册和发现 RPC 服务框架支持多种配置方式:
rpc.zookeeper.address 配置,默认地址为 127.0.0.1:2181rpc.zookeeper.connection-timeout-millis 和 rpc.zookeeper.session-timeout-millis 配置,默认分别为 15000 ms 和 60000 msrpc.server.bind-host 控制本机监听地址(默认 0.0.0.0);rpc.server.host 控制注册到 Zookeeper、供客户端连接的地址,不能配置为通配地址group、version 组成,避免字段边界冲突和 Zookeeper 路径注入;升级已有部署时,提供端和消费端必须同时使用新版本rpc.serialization 配置,支持 kryo、protostuff、hessian,默认 hessianrpc.compress 配置,当前支持 gziprpc.connect.timeout-millis 配置,默认 5000 msrpc.request.timeout-millis 配置,默认 10000 ms客户端和 Zookeeper 配置的同名 JVM 系统属性优先级高于 rpc.properties,例如:
java -Drpc.request.timeout-millis=3000 -Drpc.serialization=kryo ...
配置值会在客户端初始化时校验;未知序列化/压缩类型以及非正数超时会直接报出对应的配置键,避免运行到第一次远程调用时才失败。
原有同步接口继续通过 getProxy(HelloService.class) 创建。异步调用定义一个方法名和参数相同、返回 CompletableFuture<T> 的客户端镜像接口。group 和 version 必须与服务提供方一致:
public interface HelloServiceAsync {
CompletableFuture<String> hello(Hello hello);
}
RpcServiceConfig serviceConfig = RpcServiceConfig.builder()
.group("test1")
.version("version1")
.build();
try (NettyRpcClient transport = new NettyRpcClient()) {
RpcClientProxy clientProxy = new RpcClientProxy(transport, serviceConfig);
HelloServiceAsync asyncService = clientProxy.getAsyncProxy(
HelloServiceAsync.class, HelloService.class);
CompletableFuture<String> resultFuture = asyncService.hello(request);
resultFuture.thenAccept(System.out::println).join();
}
异步代理不会在调用线程中等待服务发现、建连或响应。同步和异步 API 共享同一套发送、超时、请求关联和错误映射逻辑;取消公开 Future 也会取消传输 Future 并清理待处理请求。
响应使用与 gRPC canonical status code 对齐的 RpcStatusCode。服务实现通过 RpcServiceException 表达可以安全返回的预期失败;客户端收到远端失败后抛出带状态码和 requestId 的 RpcRemoteException。未知服务端异常只记录完整服务端日志,客户端统一收到 INTERNAL,不会泄露底层异常消息。当前 @RpcReference 仍注入同步代理,异步镜像通过 RpcClientProxy#getAsyncProxy 显式创建。
协议版本已升级为 2,版本 1 与版本 2 的客户端、服务端不能混合部署。实现过程、取消语义、完整状态码表和测试要点见:使用 CompletableFuture 实现真正的异步 RPC 调用。
写这个 RPC 框架主要是为了通过造轮子的方式来学习,检验自己对于自己所掌握的知识的运用。
实现一个简单的 RPC 框架实际是比较容易的,不过,相比于手写 AOP 和 IoC 还是要难一点点,前提是你搞懂了 RPC 的基本原理。
我之前从理论层面在我的知识星球分享过如何实现一个 RPC。不过理论层面的东西只是支撑,你看懂了理论可能只能糊弄住面试官。咱程序员这一行还是最需要动手能力,即使你是架构师级别的人物。当你动手去实践某个东西,将理论付诸实践的时候,你就会发现有很多坑等着你。
大家在实际项目上还是要尽量少造轮子,有优秀的框架之后尽量就去用,Dubbo 在各个方面做的都比较好和完善。
Java :
CompletableFuture 的使用Netty :
ByteBuf 介绍Zookeeper :
Guide 的星球正在更新《从零开始手把手教你实现一个简单的 RPC 框架》。扫描下方二维码关注“JavaGuide”后回复 “星球”即可。

412 followers · starred Sep 2020
141 followers · starred Sep 2020
217 followers · starred Sep 2021
27 followers · starred Nov 2023
A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程。)
See the code虽说 RPC 的原理实际不难,但是,自己在实现的过程中自己也遇到了很多问题。guide-rpc-framework 目前只实现了 RPC 框架最基本的功能,一些可优化点都在下面提到了,有兴趣的小伙伴可以自行完善。
通过这个简易的轮子,你可以学到 RPC 的底层原理和原理以及各种 Java 编码实践的运用。
你甚至可以把 guide-rpc-framework 当做你的毕设/项目经验的选择,这是非常不错!对比其他求职者的项目经验都是各种系统,造轮子肯定是更加能赢得面试官的青睐。
如果你要将 guide-rpc-framework 当做你的毕设/项目经验的话,我希望你一定要搞懂,而不是直接复制粘贴我的思想。你可以 fork 我的项目,然后进行优化。如果你觉得的优化是有价值的话,你可以提交 PR 给我,我会尽快处理。
guide-rpc-framework 是一款基于 Netty、Zookeeper 和可插拔序列化组件实现的 RPC 框架。代码注释详细,结构清晰,非常适合阅读和学习。
CompletableFuture<T>RpcStatusCode、requestId 和类型化异常区分业务、超时、取消、传输与协议失败guide-rpc-framework/
├── rpc-framework-simple/ # RPC 框架核心实现
├── rpc-framework-common/ # 公共工具类和常量
├── hello-service-api/ # 示例服务接口定义
├── example-server/ # 服务提供者示例
├── example-client/ # 服务消费者示例
├── docs/ # 相关文档
└── images/ # 项目图片资源
由于 Guide 哥自身精力和能力有限,如果大家觉得有需要改进和完善的地方的话,欢迎 fork 本项目,然后 clone 到本地,在本地修改后提交 PR 给我,我会在第一时间 Review 你的代码。
我们先从一个基本的 RPC 框架设计思路说起!
注意 :我们这里说的 RPC 框架指的是:可以让客户端直接调用服务端方法就像调用本地方法一样简单的框架,比如我前面介绍的 Dubbo、Motan、gRPC 这些。 如果需要和 HTTP 协议打交道,解析和封装 HTTP 请求和响应。这类框架并不能算是“RPC 框架”,比如 Feign。
一个最简单的 RPC 框架使用示意图如下图所示,这也是 guide-rpc-framework 目前的架构 :

服务提供端 Server 向注册中心注册服务,服务消费者 Client 通过注册中心拿到服务相关信息,然后再通过网络请求服务提供端 Server。
作为 RPC 框架领域的佼佼者Dubbo的架构如下图所示,和我们上面画的大体也是差不多的。
一般情况下, RPC 框架不仅要提供服务发现功能,还要提供负载均衡、容错等功能,这样的 RPC 框架才算真正合格的。
简单说一下设计一个最基本的 RPC 框架的思路:

为了循序渐进,最初的是时候,我是基于传统的 BIO 的方式 Socket 进行网络传输,然后利用 JDK 自带的序列化机制 来实现这个 RPC 框架的。后面,我对原始版本进行了优化,已完成的优化点和可以完成的优化点我都列在了下面 👇。
为什么要把可优化点列出来? 主要是想给哪些希望优化这个 RPC 框架的小伙伴一点思路。欢迎大家 fork 本仓库,然后自己进行优化。
CompletableFuture 包装接收客户端返回结果(之前的实现是通过 AttributeMap 绑定到 Channel 上实现的) 详见:使用 CompletableFuture 实现真正的异步 RPC 调用RpcRequest和 RpcRequest 对象作为消息体,然后增加如下字段(可以参考:《Netty 入门实战小册》和 Dubbo 框架对这块的设计):
首先需要启动 Zookeeper 作为注册中心:
# 拉取 zookeeper 镜像
docker pull zookeeper:3.9.5
# 使用 Docker 启动 Zookeeper
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.9.5
git clone https://github.com/Snailclimb/guide-rpc-framework.git
cd guide-rpc-framework
mvn clean install
在 hello-service-api 模块中定义服务接口和数据传输对象:
// HelloService.java - 服务接口
public interface HelloService {
String hello(Hello hello);
}
// Hello.java - 数据传输对象
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@ToString
public class Hello implements Serializable {
private String message;
private String description;
}
在 example-server 模块中实现服务接口,使用 @RpcService 注解标记服务:
@Slf4j
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {
static {
System.out.println("HelloServiceImpl被创建");
}
@Override
public String hello(Hello hello) {
log.info("HelloServiceImpl收到: {}.", hello.getMessage());
String result = "Hello description is " + hello.getDescription();
log.info("HelloServiceImpl返回: {}.", result);
return result;
}
}
使用 @RpcScan 注解扫描服务,启动 Netty 服务器:
@RpcScan(basePackage = {"github.javaguide"})
public class NettyServerMain {
public static void main(String[] args) {
autoRegistry();
}
public static void autoRegistry() {
try (AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(NettyServerMain.class)) {
NettyRpcServer nettyRpcServer = applicationContext.getBean(NettyRpcServer.class);
HelloService helloService = applicationContext.getBean(HelloServiceImpl.class);
helloService.hello(new Hello("你好fzk", "你好服务端"));
nettyRpcServer.start();
}
}
}
使用 @RpcReference 注解注入远程服务:
@Component
public class HelloController {
@RpcReference(version = "version1", group = "test1")
private HelloService helloService;
public void test() throws InterruptedException {
String hello = this.helloService.hello(new Hello("111", "222"));
//如需使用 assert 断言,需要在 VM options 添加参数:-ea
assert "Hello description is 222".equals(hello);
Thread.sleep(12000);
for (int i = 0; i < 10; i++) {
System.out.println(helloService.hello(new Hello("111", "222")));
}
}
}
@RpcScan(basePackage = {"github.javaguide"})
public class NettyClientMain {
public static void main(String[] args) throws InterruptedException {
try (AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(NettyClientMain.class)) {
HelloController helloController = applicationContext.getBean(HelloController.class);
helloController.test();
}
}
}
127.0.0.1:2181 运行NettyServerMain.main() 方法NettyClientMain.main() 方法@RpcService:标记服务提供者,支持 group 和 version 属性用于服务分组和版本控制@RpcReference:标记服务消费者,通过 group 和 version 属性指定要调用的服务@RpcScan:指定要扫描的包路径,自动注册和发现 RPC 服务框架支持多种配置方式:
rpc.zookeeper.address 配置,默认地址为 127.0.0.1:2181rpc.zookeeper.connection-timeout-millis 和 rpc.zookeeper.session-timeout-millis 配置,默认分别为 15000 ms 和 60000 msrpc.server.bind-host 控制本机监听地址(默认 0.0.0.0);rpc.server.host 控制注册到 Zookeeper、供客户端连接的地址,不能配置为通配地址group、version 组成,避免字段边界冲突和 Zookeeper 路径注入;升级已有部署时,提供端和消费端必须同时使用新版本rpc.serialization 配置,支持 kryo、protostuff、hessian,默认 hessianrpc.compress 配置,当前支持 gziprpc.connect.timeout-millis 配置,默认 5000 msrpc.request.timeout-millis 配置,默认 10000 ms客户端和 Zookeeper 配置的同名 JVM 系统属性优先级高于 rpc.properties,例如:
java -Drpc.request.timeout-millis=3000 -Drpc.serialization=kryo ...
配置值会在客户端初始化时校验;未知序列化/压缩类型以及非正数超时会直接报出对应的配置键,避免运行到第一次远程调用时才失败。
原有同步接口继续通过 getProxy(HelloService.class) 创建。异步调用定义一个方法名和参数相同、返回 CompletableFuture<T> 的客户端镜像接口。group 和 version 必须与服务提供方一致:
public interface HelloServiceAsync {
CompletableFuture<String> hello(Hello hello);
}
RpcServiceConfig serviceConfig = RpcServiceConfig.builder()
.group("test1")
.version("version1")
.build();
try (NettyRpcClient transport = new NettyRpcClient()) {
RpcClientProxy clientProxy = new RpcClientProxy(transport, serviceConfig);
HelloServiceAsync asyncService = clientProxy.getAsyncProxy(
HelloServiceAsync.class, HelloService.class);
CompletableFuture<String> resultFuture = asyncService.hello(request);
resultFuture.thenAccept(System.out::println).join();
}
异步代理不会在调用线程中等待服务发现、建连或响应。同步和异步 API 共享同一套发送、超时、请求关联和错误映射逻辑;取消公开 Future 也会取消传输 Future 并清理待处理请求。
响应使用与 gRPC canonical status code 对齐的 RpcStatusCode。服务实现通过 RpcServiceException 表达可以安全返回的预期失败;客户端收到远端失败后抛出带状态码和 requestId 的 RpcRemoteException。未知服务端异常只记录完整服务端日志,客户端统一收到 INTERNAL,不会泄露底层异常消息。当前 @RpcReference 仍注入同步代理,异步镜像通过 RpcClientProxy#getAsyncProxy 显式创建。
协议版本已升级为 2,版本 1 与版本 2 的客户端、服务端不能混合部署。实现过程、取消语义、完整状态码表和测试要点见:使用 CompletableFuture 实现真正的异步 RPC 调用。
写这个 RPC 框架主要是为了通过造轮子的方式来学习,检验自己对于自己所掌握的知识的运用。
实现一个简单的 RPC 框架实际是比较容易的,不过,相比于手写 AOP 和 IoC 还是要难一点点,前提是你搞懂了 RPC 的基本原理。
我之前从理论层面在我的知识星球分享过如何实现一个 RPC。不过理论层面的东西只是支撑,你看懂了理论可能只能糊弄住面试官。咱程序员这一行还是最需要动手能力,即使你是架构师级别的人物。当你动手去实践某个东西,将理论付诸实践的时候,你就会发现有很多坑等着你。
大家在实际项目上还是要尽量少造轮子,有优秀的框架之后尽量就去用,Dubbo 在各个方面做的都比较好和完善。
Java :
CompletableFuture 的使用Netty :
ByteBuf 介绍Zookeeper :
Guide 的星球正在更新《从零开始手把手教你实现一个简单的 RPC 框架》。扫描下方二维码关注“JavaGuide”后回复 “星球”即可。

412 followers · starred Sep 2020
141 followers · starred Sep 2020
217 followers · starred Sep 2021
27 followers · starred Nov 2023