该项目实现了一个简易的RPC框架,通过该框架可以实现和Dubbo类似的远程服务调用功能
项目地址:https://github.com/wanger61/Rpc
项目整体主要分为三大模块:服务注册/服务发现模块,网络传输模块,Spring注解模块
本项目采用Zookeeper作为注册中心
该模块有三个主要的实现类:ServiceProviderImpl, ZkServiceRegistry, ZkServiceDiscovery
其中ZkServiceRegistry和ZkServiceDiscovery负责与Zookeeper直接交互(创建节点,获取节点内容等)
ServiceProviderImpl供外部调用,提供服务注册/服务发现功能
ServiceProviderImpl有一关键属性serviceMap,其实现是一ConcurrentHashMap。key为服务名,Object为服务的实现类。
private final Map serviceMap;
服务注册的整体过程分为两步:
第一步通过Curator客户端在Zookeeper上创建永久节点(如/my-rpc/github.javaguide.HelloService/127.0.0.1:9999)
第二步将Service实现类put至serviceMap中即可
服务发现的整体过程分为两步:
CuratorUtil中设置一Map缓存服务地址列表
private static final Map> SERVICE_ADDRESS_MAP = new ConcurrentHashMap<>();
获取服务地址时先去查缓存,若缓存中不存在再去查询注册中心(获取地址节点的子节点)
查询完注册中心后对地址节点设置监听,如果地址节点发生变化说明有服务上下线,那么就在回调中查到新的地址列表并更新缓存
获得地址列表后通过负载均衡策略选择其中一个地址
根据请求中的请求服务名,去serviceMap中查询得到服务实现类,利用反射执行服务方法
获得地址列表后需通过负载均衡策略选择其中一个地址
本项目实现了两种策略:随机算法,一致性哈希算法
一致性哈希算法的实现见:https://blog.csdn.net/wanger61/article/details/115726795?spm=1001.2014.3001.5501
本项目采用Netty进行网络传输
网络模块传输模块主要分为以下几个部分:消息实体构建,解码/编码器,服务端,客户端
创建三个消息实体类:RpcRequest,RpcResponse,RpcMessage
RpcRequest对应服务调用请求,包含:请求ID+服务接口名+服务方法名+请求参数+请求参数类型+版本号+group(当接口有多个实现类时用于标识)
RpcResponse对应服务调用响应,包含:请求ID+响应码+响应信息+服务调用结果数据
RpcMessage用于封装消息,是网络中传输的实际类型,包含:消息类型(心跳ping,心跳pong,RpcRequest,RpcResponse)+序列化类型+压缩类型+具体数据(RpcRequest或RpcResponse)
解码/编码器需要负责将RpcMessage转换成字节进行网络传输,在接收时将字节重新构建回RpcMessage
另外,由于该框架采用TCP传输,还需要解决TCP的粘包半包问题
为解决以上问题,需采用自定义协议,将消息分为消息头和消息体
消息头定义为:
* 4B magic code(魔数) 1B version(版本) 4B full length(消息长度) 1B messageType(消息类型)
* 1B codec(序列化类型) 1B compress(压缩类型) 4B requestId(请求的Id)
* body(object类型数据)
编码时按照协议规定顺序输出各个字节,解码时按照相同顺序读入各个字节
自定义解码器继承自LengthFieldBasedFrameDecoder,该类可以根据协议中的长度字段读取相应长度的字节,即整个包。从而解决了TCP粘包/半包问题
public class RpcMessageDecoder extends LengthFieldBasedFrameDecoder {
public RpcMessageDecoder() {
this(RpcConstants.MAX_FRAME_LENGTH, 5, 4, -9, 0);
}
.......
}
序列化/反序列化采用Kryo框架,该框架使用较简单,但需注意Kryo不是线程安全的,应通过ThreadLocal获取
压缩/解压缩采用JDK的GZIPOutputStream和GZIPInputStream
服务端通过ServerBootstap创建,并在Childpipeline中添加:IdleStateHandler心跳处理器,解码编码器,NettyRpcServerHandler业务处理器
业务处理器处理三类事件:
客户端主要实现服务调用请求的发送和请求结果的接收
客户端通过Bootstrap创建,在pipeline中添加IdleStateHandler心跳处理器,编码解码器,NettyRpcClientHandler业务处理器
发送服务调用请求的步骤:
难点:通过上述方法异步发送RpcRequest后,RpcResponse只能在NettyRpcClientHandler中通过read方法接收,那么该如何获取请求结果呢
解决方案:通过CompletableFuture异步获取请求
步骤:
通过自定义注解完成服务注册和服务调用
设计两个注解:@RpcService和@RpcReference
@RpcService注解用于服务注册,被标注的类会被自动注册,如:
@RpcService
public class HelloServiceImpl implements HelloService {
......
}
@RpcReference注解用于服务调用,标注在属性上,调用该属性的方法时会以Rpc方式调用远程服务。如:
@Component
public class HelloController {
@RpcReference
private HelloService helloService;
}
通过实现自定义后置处理器RpcBeanPostProcessor完成以上注解功能
重写Object postProcessBeforeInitialization(Object bean, String beanName)
方法:
重写Object postProcessAfterInitialization(Object bean, String beanName)
方法:
实现一个类获取动态代理对象,该类继承自InvocationHandler
对invoke(Object proxy, Method method, Object[] args)
方法进行重写:
获取代理对象方法:
public T getProxy(Class clazz){
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class>[]{clazz}, this);
}
后续调用该属性的方法时实际会去调用上面的invoke方法,进行Rpc远程调用