手写RPC
手动编写一个RPC框架,思路如下:
- 主要角色:使用者、服务提供端、服务消费端、注册中心(zookeeper)
- 实现目标:使用者引入我们的jar包,启动服务之后,需要作为生产者启动一个等待连接的server端口,同时也要作为一个消费者去获取其它的生产者的信息并调用
- 服务提供端的流程:
- 获取所有需要暴露的接口。
- 将需要暴露的接口注册到注册中心。
- 启动sockerserver等待连接。
- 添加编码、解码的handler。
- 服务消费端的流程:
- 从注册中心拉取所有提供者的信息,保存到缓存中(redis、本地..)
- 需要监听注册中心的变化,保证缓存中数据的有效性。
- 找到所有需要远程调用的接口,并且为其生成代理类(实现发送请求和接收返回数据)。
以下记录下实现的关键步骤:
服务端启动
服务端启动只需要做两件事,注册和启动socket监听端口。
1 | // 服务注册 |
获取所有需要暴露的接口
自定义一个注解(RegistryAnno),使用者在接口实现上使用了该注解,则认为该接口需要暴露,并且将其注册到zk。
1 | public void serviceRegistry() throws Exception { |
启动RPC服务端
- 采用Reactor 1+M+N 的线程模型。
- 此处需要注意handler的顺序,head在出去方向,tail在进入方向,addList则是在head后面的handler链上继续添加。
- handler一次解码和二次编码都是使用netty自带的编解码器,以消息开始的4个字节记录消息的长度,二次解码则是将bytebuf转换为java对象,一次编码则是将java对象转换为bytebuf。
1 | public void start() { |
服务消费端启动
服务消费端启动只需要拉取注册中心的服务列表。
1 | public void discover() throws Exception { |
生成代理类
- 使用者在外部接口时,此时容器中是没有接口对象的,需要在使用者在controller、service等添加容器时就生成代理对象并赋值。
- 使用postprocessor后置处理器中实现。
- 自定义一个注解(DiscoveryAnno),使用者在使用外部接口时标记该接口是外部的接口。
- 此处使用cglib代理。
1 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
发送请求
- 发送请求时需要从本地缓存中获取到提供方的信息,IP,端口等,用netty进行连接,并发送消息。
- 需要注意返回消息时,需要阻塞等待消息返回,并且需要区分返回的消息属于哪一个请求(此处使用promise + requestId实现)
- 发送请求时handler的顺序需要注意,此处与提供者不一样,head在出去server方向,tail在自己方向。
1 | public RpcResponse sendRequest(RpcRequest rpcRequest) throws Exception { |
使用者
- 提供方需要标明需要暴露的接口
1 |
|
- 消费方需要标明外部接口
1 |
|