在上一篇博客中,介绍了RPC的主要概念和实现原理,然后基于TCP协议实现了一个非常简单的RPC小案例(点此回顾)。
现在,自我挑战一下,动手写一个RPC框架。
高能预警:本文涉及到的知识点如下
- Spring Boot2:起步依赖、自动配置,让应用开发变得简单
- Spring的Java Bean配置,条件注解:灵活控制注入
- 基于JDK接口的动态代理(了解一下?):发起远程调用对服务消费者来说是透明的
- ZooKeeper(了解一下?):提供树形目录结构,节点变化函数回调
- Netty(了解一下?):高性能的网络通信
- 自定义注解:哪些类的实例提供远程服务,又是实例的哪些属性需要注入远程服务
- Java内置的序列化(了解一下?):网络中数据以二进制的形式进行传输
先看下效果
1、配置文件:开启服务注册、ZK地址和连接超时时间、暴露服务端口。
2、服务提供者:用RpcService注解说明该bean提供远程调用服务。
3、服务消费者:RpcReference注解为此属性注入远程调用服务的代理对象。
4、注册中心:服务提供者启动后,在ZK成功创建了相关节点。
5、调用服务:成功发起了一次远程过程调用。
嘀,开车啦!
嗤,源码下载地址:https://download.csdn.net/download/qq_31142553/10913525
一、原理&流程
敲黑板讲重点!!!
跟Dubbo一样,分为服务提供者、服务消费者、注册中心三个主要角色。后面将逐一进行讲解。
镇楼图:
二、代码结构
我们将项目分为两个模块,一个核心模块,一个测试模块。大概代码结构如下
主要maven依赖
<!-- lombok,简化实体类结构 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <optional>true</optional> -->
</dependency>
<!-- netty,高性能服务通信 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- zk,注册中心 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
</dependency>
三、角色详解
因为基于Java Bean的配置比较灵活,所以我们建立一个全局配置类,在这里配置需要交给Spring IOC容器管理的对象。但是,如果系统不打算使用RPC服务,那就没必要加载这个配置类了。因此,我们自定义一个注解,如果在Spring Boot启动类上使用了此注解,那么此全局配置类才生效。
1、服务提供者
概述
服务提供者系统启动时,如果启用了服务注册功能,就将提供服务的bean信息注册到ZooKeeper,并在内存维护一个interfaceName-bean的映射关系集合,最后在指定端口开启一个监听远程调用请求的服务。
详解
是否启用服务注册功能,可以从配置属性读取。采用条件注解的方式,如果启用了,就注册一个专门用于维护与ZooKeeper的连接会话的实例到IOC容器,在构造函数里面初始化它的ZK实例,并判断是否需要创建ZK的服务根节点。然后,还要注册一个RPC服务到IOC容器,这个服务在Spring容器加载完成之后,获取所有带了自定义的、表示提供远程服务的注解的bean,先将它们存到本地内存维护的一个Map集合里面,以接口全类名为key,bean实例为value,这样,远程过程调用的请求过来之后,就从这个map里面根据接口名找到对应的bean来处理了。然后,还要将这些bean实例信息放到ZK上面,以提供服务消费方去查询获取。至于存什么信息呢?首先是需要这些bean的接口全类名,然后是本机启动的监听请求服务的IP和端口。所以,在ZK服务根节点下,创建一个以接口全类名为名称的永久节点,然后创建一个临时序列子节点,以本机IP和监听端口、当前时间戳(处理一台服务器的一个接口有多个实现类的问题)命名。最后,还要启动一个Netty服务端,绑定本机的IP和端口,端口可以写到配置里面、与前文说的保持一致,添加业务处理的Handler,业务处理主要是根据请求的接口名、方法名、参数列表找到本地内存维护的map里面的服务实例,通过反射调用并返回执行结果。
2、服务消费者
概述
服务启动时,从注册中心订阅(先查,后续变化自动触发更新)自己需要的服务到本地列表。方法调用时,利用代理先去本地列表查询到服务提供所在IP和端口,然后发起Netty请求,将响应作为远程调用的执行结果返回。
详解
首先,服务消费者系统启动时,先注册一个与ZooKeeper的连接会话的实例到IOC容器。然后,查询所有bean中带了自定义的、表示需要远程服务的注解的属性,根据它们的类型名称到ZK上面查询同名节点,存在的话获取它的所有孩子节点名称,然后保存到一个自己定义的、类似Map<String, Set<String>>结构的集合里,同时还要为类名的节点注册子节点变化监听器,触发时重新读取其子节点更新到集合里。接着,利用反射为属性赋值一个代理对象,这个代理对象在方法被调用时,将方法调用信息封装成请求参数,根据自己的类型名称到集合里面找到符合的服务提供者所在的IP和端口后发起请求,并将响应结果返回。
四、代码说明
由于代码量有点大,我也不知道怎么贴。就将这些类是做什么用的简单说明一下,比较关键的部分附上截图。
推荐感兴趣的小伙伴直接去下载源码:https://download.csdn.net/download/qq_31142553/10913525
(1)annotation包
EnableRpcConfiguration:自定义注解,用于Spring Boot启动类上面。有此注解时,配置类RpcConfiguration才生效,系统才使用RPC功能。
RpcReference:自定义注解,用于Spring Bean的属性上,表明此字段需要注入远程调用的代理对象。
RpcService:自定义注解,用于Spring Bean的类上面,表明该实例提供远程调用服务。需要设置interfaces属性,指明提供哪些接口的服务。
(2)client包
NodeChildrenChangedWatcher:子节点变化观察者,用于服务消费者监听ZooKeeper指定节点的变化从而更新服务信息列表。
RpcClientHandler:用于服务消费者向服务提供者发送请求并接收响应。
RpcInjectHandler:客户端核心类,从ZooKeeper获取需要的服务信息,为带RpcReference注解的属性设置远程调用的代理对象。
(3) common包
RpcDecoder:Netty解码器,将字节数组转为Java对象。
RpcEncoder:Netty编码器,将Java对象转为字节数组。
RpcRequest:RPC请求封装类,有接口名称、方法名称、参数列表(类型和值)。
RpcResponse:RPC结果封装类,有响应结果、异常。
(4)conf包
Consts:常量,有zk服务根节点 、本机IP。
RpcConfiguration:RPC配置类,配置所有注入IOC容器的bean。
(4)server包
RpcServer:将提供RPC服务的bean存到本地服务列表,并注册到ZooKeeper
RpcServerHandler:处理服务消费者的请求,反射调用本地方法,并将执行结果返回。
ServerListener:开启Netty服务端监听。
(5)util包
HashMultimap:类似Google Guava的同名类,提供Map<K, Set<V>>结构的集合。
IpUtils:获取本机IP地址。
RpcException:RPC异常类。
SerializationUtils:Java内置的序列化和反序列化功能。
(6)zk包
ZkClient:维护服务消费者与ZooKeeper的连接会话。
ZkServer:维护服务提供者与ZooKeeper的连接会话,并创建服务根节点(需要的话)。
后续可以考虑加入负载均衡、服务容错等功能!
源码下载地址:https://download.csdn.net/download/qq_31142553/10913525