简易RPC框架:需求与设计

1 需求分析

RPC 全称 Remote Procedure Call ,简单地来讲,它能让使用者像调用本地方法同样,调用远程的接口,而不须要关注底层的具体细节。编程

例如车辆违章代办功能,若是车辆由于某种缘由违章,只须要经过这个违章代办功能(它也许是个APP),咱们就能动动手指,而省去了一些跑腿的工做。安全

不像微服务背景下你们所说的 RPC 框架,如 Dubbo 之类。这个 RPC 框架不提供过多的关于服务注册、服务发现、服务管理等功能。它针对的是这样的一些场景:在内部网络,或者局域网内,两个属于同个业务的系统之间须要通讯,而咱们又以为去设计多一种二进制网络协议过于繁琐而且没有必要,这时候若是给客户端开发者一些明确的接口,让他知道实现什么功能该调用什么接口,那么省去的工做量以及开发效率上的提高不言而喻。微信

这个 RPC 系统基于 Java 语言实现,需求以下:网络

  • RPC 服务端能够经过一条长链接发布多个接口(Interface),客户端按需生成对应接口的代理。
  • RPC 客户端也能够发布接口,以便在必要的时候,服务端能够主动调用客户端的接口实现
  • 客户端与服务端之间保持长链接而且维持心跳
  • 服务端针对不一样的接口实现,能够指定不一样的线程池去处理
  • 序列化协议支持扩展
  • 通讯协议与具体编程语言无关
  • 支持并发调用,一个RPC客户端实例要求是线程安全的

2. 通讯协议设计

高效的通讯协议通常是二进制格式的,比较常见的还有文本协议好比说HTTP,为了追求效率,这个 RPC 框架就采用二进制格式。并发

协议的基本要素

魔数

要了解到,报文是在网络上传输的,安全性比较低,所以有必要采起一些措施使得并非任何人均可以随随便便往咱们的端口上发东西,所以咱们对报文要有一个初步的识别功能,这时候“魔数(magic number)”就派上用场了。魔数并不受任何规范约束,没有人能够要求你的魔数应该遵循什么规范,实际上魔数只是咱们通讯双方都约定的一个“暗号”,不知道这个暗号的人就没法参与进通讯中。例如 Java 源文件编译后的 class 文件开头就有一个魔数:0xCAFEBABE,随随便便打开一个class文件用十六进制编辑器查看,就能看到。负载均衡

class文件

Java 虚拟机加载 class 的时候会先验证魔数。若是不是 CAFEBABE 就认为是不合法的 class 文件,并拒绝加载。框架

不过魔数起到的安全防范做用是很是有限的,“有心人”能够经过抓取网络包就识别出魔数了。所以魔数这个东西实际上是“防君子不防小人”。编程语言

协议版本

一个协议可能也会有多个版本,例如说 HTTP1.0 和 HTTP1.1,不一样版本的协议元素可能发生了改变,解析方式也会发生改变,所以协议设计这一块,须要预留出地方声明协议的版本,通讯双方在解析协议或者拼装协议的时候才有迹可循。编辑器

报文类型

对于RPC框架来讲,报文可能有多种类型:心跳类型报文、认证类型报文、请求类型报文、响应类型报文等。ide

上下文 ID

RPC 调用实际上是一个“请求-响应”的过程,而且跨物理机器,所以每次请求和响应,都必须带上上下文 ID,通讯双方才能把请求和响应对应起来。

状态

状态用来标识一次调用时正常结束仍是异常结束,一般由被调用方置状态。

请求数据

即发送到服务端的调用请求,一般是序列化后的二进制流,长度不定。

长度编码字段

收报文的一方怎么知道发报文的那一方发了多少字节呢?所以发送方必须在协议里告诉接收方须要接受多少字节才算一个完整的报文。

保留字段

协议一旦被设计,并不是一成不变的,往后可能有变更的可能,所以还须要考虑保留一些字节空间做为保留字段,以备往后协议的扩展。

协议设计

结合以上的一些设计原则,具体协议设计以下:

------------------------------------------------------------------------
 | magic (2bytes) | version (1byte) |  type (1byte)  | reserved (7bits) | 
 ------------------------------------------------------------------------
 | status (1byte) |    id (8bytes)    |        body length (4bytes)     |
 ------------------------------------------------------------------------
 |                                                                      |
 |                   body ($body_length bytes)                          |
 |                                                                      |
 ------------------------------------------------------------------------

3. 链路可靠性

客户端与服务端之间的链接采用 TCP 长链接,一个客户端与服务端之间保持至少一条长链接。接口调用请求的发送,在多条链接之间进行负载均衡。

每条链接在空闲的时候,由客户端主动向服务端发送心跳报文,而且客户端在发现链接失效或断开的时候,自动进行重连。

每一个客户端向服务端创建链接后,在正式发起接口调用请求以前,都须要进行check in 操做, check in 操做主要是将客户端的身份标识(identifier)和客户端的心跳间隔告诉服务端。利用 netty 的 handler 责任链机制和自带的 IdleStateHandler,自动检测出链接是否空闲,并在空闲时触发心跳报文的发送。而服务端在客户端 checkin 后,根据客户端的心跳频率,在本身的 handler pipeline 上动态加入一个 IdleStateHandler,来检测出客户端是否已经失联,若是是,则主动关闭链接。

同时,客户端本地将会起一个定时执行任务的线程,按期检查链接是否失效,若是失效,则关闭旧链接,并进行链接的重建。

扫一扫关注个人微信公众号

相关文章
相关标签/搜索