一文彻底弄懂 RPC 中的协议和序列化
一、协议
协议的作用
我们知道 RPC 需要将对象序列化成二进制数据,写入本地 Socket 中,然后被网卡发送到网络设备中进行网络传输。但是在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(同一个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。对于服务提供方来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢?
所以我们需要对 RPC 传输数据的时候进行“断句”,在应用发送请求的数据包里面加入“句号”,这样接收方应用数据流里面分割出正确的数据,“句号”就相当于是消息的边界,用于标识请求数据的结束位置。于是需要在发送请求的时候定一个边界,在请求收到的时候按照这个设定的边界进行数据分割,避免语义不一致的事情发生,而这个边界语义的表达,就是所谓的协议。
协议的设计
我们知道协议的作用之后怎么设计一个协议呢?或者说为啥不直接使用现有的 HTTP 协议呢?
相对于 HTTP 的用处,RPC 给更多的是负责应用间的通信,所以性能要求更高。但是 HTTP 协议的数据包大小相对于请求数据本身要大很多,有需要加入很多无用的内容,比如换行符号、回车符等等;还有一个重要的原因就是 HTTP 协议属于无状态协议,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完之后再关闭连接。所以对于要求高性能的 RPC 来说,HTTP 协议很难满足需求,RPC 会选择设计更紧凑的私有协议。
上面我们说了需要对传输的数据进行“断句”来确定消息边界,由于 RPC 每次发请求的大小都是不固定的,所以我们的协议必须能让接收方正确的读出不定长的内容。可以固定一个长度(比如4字节)用来保存请求数据大小,这样接收到数据的时候,可以先读取固定长度位置里面的值,值的大小就代表协议的长度,接着根据值的大小来