0 前言
提早先祝你们春节快乐!好了,先简单聊聊。git
我从事的是大数据开发相关的工做,主要负责的是大数据计算这块的内容。最近Hive集群跑任务老是会出现Thrift链接HS2相关问题,研究了解了下内部原理,忽然来了兴趣,就想着本身也实现一个RPC框架,这样可让本身在设计与实现RPC框架过程当中,也能从中了解和解决一些问题,进而让本身可以更好的发展(哈哈,会不会说我有些剑走偏锋?不去解决问题,竟然研究RPC。别急,这类问题已经解决了,后续我也会发文章详述的)。github
1 RPC流水线工程?
原理图上我已经标出来流程序号,咱们来走一遍:算法
- ① Client以本地调用的方式调用服务
- ② Client Stub接收到调用后,把服务调用相关信息组装成须要网络传输的消息体,并找到服务地址(host:port),对消息进行
编码
后交给Connector进行发送 - ③ Connector经过网络通道发送消息给Acceptor
- ④ Acceptor接收到消息后交给Server Stub
- ⑤ Server Stub对消息进行
解码
,并根据解码的结果经过反射
调用本地服务 - ⑥ Server执行本地服务并返回结果给Server Stub
- ⑦ Server Stub对返回结果组装打包并
编码
后交给Acceptor进行发送 - ⑧ Acceptor经过网络通道发送消息给Connector
- ⑨ Connector接收到消息后交给Client Stub,Client Stub接收到消息并进行
解码
后转交给Client - ⑩ Client获取到服务调用的最终结果
因而可知,主要须要RPC负责的是2~9这些步骤,也就是说,RPC主要职责就是把这些步骤封装起来,对用户透明,让用户像调用本地服务同样去使用。api
2 为RPC作个技术选型
-
序列化/反序列化缓存
首先排除Java的ObjectInputStream和ObjectOutputStream,由于不只须要保证须要序列化或反序列化的类实现
Serializable
接口,还要保证JDK版本一致,公司应用So Many,使用的语言也众多,这显然是不可行的,考虑再三,决定采用Objesess。服务器 -
通讯技术网络
一样咱们首先排除Java的原生IO,由于进行消息读取的时候须要进行大量控制,如此晦涩难用,正好近段时间也一直在接触Netty相关技术,就再也不纠结,直接命中Netty。数据结构
-
高并发技术多线程
远程调用技术必定会是多线程的,只有这样才能知足多个并发的处理请求。这个能够采用JDK提供的Executor。架构
-
服务注册与发现
Zookeeper。当Server启动后,自动注册服务信息(包括host,port,还有nettyPort)到ZK中;当Client启动后,自动订阅获取须要远程调用的服务信息列表到本地缓存中。
-
负载均衡
分布式系统都离不开负载均衡算法,好的负载均衡算法能够充分利用好不一样服务器的计算资源,提升系统的并发量和运算能力。
-
非侵入式
借助于Spring框架
RPC架构图以下:
3 让RPC梦想成真
由架构图,咱们知道RPC是C/S结构的。
3.1 先来一个单机版
单机版的话比较简单,不须要考虑负载均衡(也就没有zookeeper),会简单不少,可是只能用于本地测试使用。而RPC总体的思想是:为客户端建立服务代理类,而后构建客户端和服务端的通讯通道以便于传输数据,服务端的话,就须要在接收到数据后,经过反射机制调用本地服务获取结果,继续经过通讯通道返回给客户端,直到客户端获取到数据,这就是一次完整的RPC调用。
3.1.1 建立服务代理
能够采用JDK原生的Proxy.newProxyInstance和InvocationHandler建立一个代理类。详细细节网上博客众多,就不展开介绍了。固然,也能够采用CGLIB字节码技术实现。
3.1.2 构建通讯通道 & 消息的发送与接收
客户端经过Socket和服务端创建通讯通道,保持链接。能够经过构建好的Socket获取ObjectInputStream
和ObjectOutputStream
。可是有一点须要注意,若是Client端先获取ObjectOutputStream
,那么服务端只能先获取ObjectInputStream
,否则就会出现死锁一直没法通讯的。
3.1.3 反射调用本地服务
服务端根据请求各项信息,获取Method,在Service实例上反向调用该方法。
3.2 再来一个分布式版本
咱们先从顶层架构来进行设计实现,也就是技术选型后的RPC架构图。主要涉及了借助于,Zookeeper实现的服务注册于发现。
3.2.1 服务注册与发现
当Server端启动后,自动将当前Server所提供的全部带有@ZnsService
注解的Service Impl注册到Zookeeper中,在Zookeeper中存储数据结构为 ip:httpPort:acceptorPort
当Client端启动后,根据扫描到的带有@ZnsClient
注解的Service Interface从Zookeeper中拉去Service提供者信息并缓存到本地,同时在Zookeeper上添加这些服务的监听事件,一旦有节点发生变更(上线/下线),就会当即更新本地缓存。
3.2.2 服务调用的负载均衡
Client拉取到服务信息列表后,每一个Service服务都对应一个地址list,因此针对连哪一个server去调用服务,就须要设计一个负载均衡路由算法。固然,负载均衡算法的好坏,会关系到服务器计算资源、并发量和运算能力。不过,目前开发的RPC
框架zns
中只内置了Random
算法,后续会继续补充完善。
3.2.3 网络通道
- Acceptor
当Server端启动后,将同时启动一个Acceptor
长链接线程,用于接收外部服务调用请求。内部包含了编解码以及反射调用本地服务机制。
- Connector
当Client端发起一个远程服务调用时,ZnsRequestManager
将会启动一个Connector
与Acceptor
进行链接,同时会保存通道信息ChannelHolder
到内部,直到请求完成,再进行通道信息销毁。
3.2.4 请求池管理
为了保证必定的请求并发,因此对服务调用请求进行了池化管理,这样能够等到消息返回再进行处理,不须要阻塞等待。
3.2.5 响应结果异步回调
当Client端接收到远程服务调用返回的结果时,直接通知请求池进行处理,No care anything!
4. 总结
本次纯属是在解决Thrift链接HS2问题时,忽然来了兴趣,就构思了几天RPC大概架构设计状况,便开始天天晚上疯狂敲代码实现。我把这个RPC框架命名为zns
,如今已经完成了1.0-SNAPSHOT
版本,能够正常使用了。在开发过程当中,也遇到了一些平时忽略的小问题,还有些是工做工程中没有遇到或者遗漏的地方。由于是初期,因此会存在一些bug,若是你感兴趣的话,欢迎提PR和ISSUE,固然也欢迎把代码clone到本地研究学习。虽然就目前来看,想要作成一个真正稳定可投产使用的RPC框架还有短距离,可是我会坚持继续下去,毕竟RPC真的涉及到了不少点,只有真正开始作了,才能切身体会和感觉到。Ya hoh!终于成功实现了v1.0,嘿嘿……
源码地址
-
zns源码简单介绍:
zns
由zns-api
,zns-common
,zns-client
,zns-server
四个核心模块组成。zns-service-api
,zns-service-consumer
,zns-service-provider
三个模块是对zns
进行测试使用的案例。