可扩展且向后兼容的协议

1,280 阅读3分钟

协议的作用

但在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器 上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一 个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。

举个具体例子,调用方 发送 AB、CD、EF 3 个消息,如果没有边界的话,接收端就可能收到 ABCDEF 或者 ABC、DEF 这样的消息,这就会导致接收的语义跟发送的时候不一致了.

协议的作用: 为了避免语义不一致的事情发生,我们就需要在发送请求的时候设定一个边界,然 后在收到请求的时候按照这个设定的边界进行数据分割。这个边界语义的表达,就是我们所 说的协议。

如何设计协议

HTTP协议和RPC协议都属于应用层协议,为什么不直接使用现成的HTTP协议?

1. RPC负责应用间的通信,性能要求更高

2. HTTP协议的数据包相对请求数据本身大很多,加入了很多无用的内容,比如换行符号\回车符合

3. HTTP协议属于无状态协议,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完成后再关闭连接

怎么设计一个私有RPC协议

问题:每次RPC发送请求的大小都是不固定的,如何让接收方正确的读出不定长的内容.

思路: 先固定一个长度(比如4个字节)用来保存整个请求的数据大小,接收到数据的时候,先读取固定长度的位置里面的值,值的大小就代表协议体的长度,接着再根据值的大小读取协议体的数据

解决了数据切割的问题,对于服务提供方来说,不知道这个协议体里面的二进制数据是通过哪种序列化方式生成的,所以无法还原出正确的语义,也不能把二进制还原成对象.


因此我们把序列化方式单独拿出来,类似于协议长度一样用固定长度存放,这些固定存放的参数统称为"协议头".

在协议头里面,我们除了会放协议长度、序列化方式,还会放一些像协议标示、消息 ID、 消息类型这样的参数,而协议体一般只放请求接口方法、请求的业务参数值和一些扩展属 性.

可扩展的协议

可扩展协议关键在于让协议头支持可扩展,扩展后的协议头的长度就不能定长了.实现读取不定长的协议头内容,在这之前肯定需要一个固定的地方读取长度,所以我们需要一个固定的写入协议头的长度.整个协议变成三部分内容:固定部分\协议头内容\协议体内容,前面两部分统称为"协议头".

问题:  RPC怎么实现请求跟响应关联?

以 Dubbo 为例,消费者发送请求时,使用 AtomicLong 自增,产生一个 消息 ID。由于 Dubbo 底层 IO 操作是异步的,Dubbo 发送请求之后,需要阻塞等待消费者返回信息。 消费者会将消息 ID 保存到 Map 结构中,。为了保证请求响应可以一一对应,这就需要提供 者返回的响应信息带上请求者消息 ID。 通过响应的消息 ID,通过 上面提到 Map 存储数 据,就能找到对应的请求.