协议的作用
在网络传输过程中需要采用二进制传输,因此RPC请求在发送到网络中之前,需要把方法调用的请求参数转成二进制;转成二进制后,写入本地 Socket 中,然后被网卡发送到网络设备中。但在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包。因此,需要正确区分网络包的起始和终止位置,保证不产生歧义。而协议就是解决上述问题的。
如何设计协议
为什么不适用现成的HTTP协议
(1)相对于 HTTP 的用处,RPC 更多的是负责应用间的通信,所以性能要求相对更高。但 HTTP 协议的数据包大小相对请求数据本身要大很多,又需要加入很多无用的内容,比如换行符号、回车符等;
(2)HTTP 协议属于无状态协议,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完成后再关闭连接。
RPC协议包含的内容
定长协议
(1)首先解决消息边界问题。RPC每次发送请求的大小都是不固定的,协议必须能让接收方正确地读出不定长的内容。可以固定一个长度用来存储整个请求的大小,接收数据之后,先读取固定长度的位置信息,根据读取内容判断协议体大小。
(2)解决序列化类型:RPC支持的序列化类型很多,若对方不知道使用的序列化类型,就无法正确反序列化。因此,需要把序列化方式单独存储起来,类似于解决消息边界问题
(3)在协议头里面,除了会有协议长度、序列化方式,还会放一些像协议标示、消息 ID、消息类型这样的参数,而协议体一般只放请求接口方法、请求的业务参数值和一些扩展属性。这样一个完整的 RPC 协议大概就出来了,协议头是由一堆固定长度参数组成,而协议体是根据请求接口和参数构造的,长度属于可变的,具体协议如下图所示:
可扩展协议
定长协议无法向协议头添加新参数,如果添加将会导致线上兼容性的问题。如果简单的将新添加的参数放到协议头中,将协议头字段增加,将会导致协议体反序列化错误;如果将新添加的参数放在协议体中,每次读取新参数需要先将协议体反序列化,成本高。解决的关键问题在于让协议头支持可扩展,扩展后协议头的长度就不能定长了。因此,整体协议就变成了三部分内容:固定部分、协议头内容、协议体内容,前两部分我们还是可以统称为“协议头”,具体协议如下:
整体设计原则
怎么去设计一个可“升级”的协议。不仅要让我们在扩展新特性的时候能做到向下兼容,而且要尽可能地减少资源损耗,所以我们协议的结构不仅要支持协议体的扩展,还要做到协议头也能扩展。