rpc框架简易实现

dubbo,作为一个成熟、易用、多支持的远程方法调用框架,应用广泛。为了透彻理解dubbo,我们今天来实现一把自己的dubbo。

分析

为了实现dubbo,我们需要做哪些事情呢?

  1. 远程,必然涉及到网络通信,这个我们用成熟的netty框架来做
  2. 方法调用转为网络请求,客户端的方法调用,可以通过aop将行为转为网络请求。
    那么,网络请求要做哪些事情呢?要调用远程方法,这就意味着服务端需要知道调用的类、方法以及方法参数值。
    所以我们封装了一个远程调用信息类来进行交互:RpcInfo(还有表示返回结果的result、success、exception等属性),在进行网络通信时,数据通过Kryo的方式序列化(因为它占用空间少、速度快,但是它要求序列化的类必须有无参的构造方法)
  3. 方法调用与响应结果反馈是不同的线程,如何拿到返回结果数据。
    这里需要一个通讯机制,本项目采用简单的方式,应用中维护一个map,在发送前,生成id作为key,然后将调用信息对象放入到map中,接着通过该调用信息对象wait,阻塞。在调用结果返回时,调用的线程就会被唤醒,根据最新的返回结果来给方法调用者反馈。

实现步骤

1、netty框架

首先客户端与服务端的netty使用代码
服务端:
在这里插入图片描述

客户端:
在这里插入图片描述

2、触发网络请求

方法调用转化为网络请求,此处不做aop实现,采用直接调用的形式(也就是aop拦截器中会做的事情)
在这里插入图片描述

3、错误信息反馈

如果从invoke开始直到拿到结果的过程中出错,怎么提示给客户端?我们将错误信息封装到RpcInfo中,在出现异常的时候,就将该对象写入到结果中。
在这里插入图片描述

4、异常信息的唯一请求id

但是netty的exceptionCaught方法是没有数据信息参数的,在返回异常信息的时候标示客户端请求的唯一id拿不到,怎么办?

很容易想到的一种方案是在服务端读到id信息之后就写入threadLocal中,之后exceptionCaught方法中从threadLocal取就好,笔者一开始也是这么想的,但是后来发现netty的handler链路不一定是在一个线程中:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以需要加上对同一个请求handler链路跨越多个线程的支持,笔者的实现方案是在channelRead,write前后都加上try catch,这样就不会走到exceptionCaught中。

想到rpc实现比如dubbo可以增加许多用户自定义filter,也为了方便通过handler给本rpc实现添加更多的特征,将这部分代码进行了抽取,提取出一个ChannelHandlerAdapter基类,本实现中用到的ChannelHandlerAdapter都继承该基类(读取在反序列化之前,写操作在序列化之后的除外)。
在这里插入图片描述

在这里插入图片描述

5、客户端与服务端handler

接下来的就比较好理解了
客户端的handler
在这里插入图片描述

服务端端handler
在这里插入图片描述

6、序列化的handler

在这里插入图片描述

代码详见:https://github.com/guzhangyu/practice-Code/tree/master/src/main/java/com/phei/netty/rpc