dubbo,作为一个成熟、易用、多支持的远程方法调用框架,应用广泛。为了透彻理解dubbo,我们今天来实现一把自己的dubbo。
分析
为了实现dubbo,我们需要做哪些事情呢?
- 远程,必然涉及到网络通信,这个我们用成熟的netty框架来做
- 方法调用转为网络请求,客户端的方法调用,可以通过aop将行为转为网络请求。
那么,网络请求要做哪些事情呢?要调用远程方法,这就意味着服务端需要知道调用的类、方法以及方法参数值。
所以我们封装了一个远程调用信息类来进行交互:RpcInfo(还有表示返回结果的result、success、exception等属性),在进行网络通信时,数据通过Kryo的方式序列化(因为它占用空间少、速度快,但是它要求序列化的类必须有无参的构造方法) - 方法调用与响应结果反馈是不同的线程,如何拿到返回结果数据。
这里需要一个通讯机制,本项目采用简单的方式,应用中维护一个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