协议:怎么设计可扩展且向后兼容的协议?

浏览器收到命令后会封装一个请求,并把请求发送到 DNS 解析出来的 IP 上,通过抓包工具我们可以抓到请求的数据包,如下图所示:

协议的作用

RPC 请求在发送到网络中之前,他需要把方法调用的请求参数转成二进制;转成二进制后,写入本地 Socket 中,然后被网卡发送到网络设备中。

但在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。对于服务提供方应用来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢?

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

如何设计协议

从 RPC 的作用说起,相对于 HTTP 的用处,RPC 更多的是负责应用间的通信,所以性能要求相对更高。但** HTTP 协议的数据包大小相对请求数据本身要大很多,又需要加入很多无用的内容,比如换行符号、回车符等;还有一个更重要的原因是,HTTP 协议属于无状态协议**,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完成后再关闭连接。因此,对于要求高性能的 RPC 来说,HTTP 协议基本很难满足需求,所以 RPC 会选择设计更紧凑的私有协议。

那怎么设计一个私有 RPC 协议呢?

RPC 每次发请求发的大小都是不固定的,所以我们的协议必须能让接收方正确地读出不定长的内容。

先设计一个固定长度的:先固定一个长度(比如 4 个字节)用来保存整个请求数据大小,读取固定长度的位置里面的值,值的大小就代表协议体的长度,接着再根据值的大小来读取协议体的数据,整个协议可以设计成这样:

不定长协议:



但这种协议只是实现了正确的断句效果,在RPC中是行不通的,因为服务提供方并不知道它的序列化方式是什么,也就不能将二进制数据还原成对象。因此,需要将序列化方式单独拿出来,类似协议长度一样用固定的长度存放,这些需要固定长度存放的参数统称为“协议头”,整个协议就被拆分为:协议头协议体两部分。

在协议头里面,我们除了会放协议长度、序列化方式,还会放一些像协议标示、消息 ID、消息类型这样的参数,而协议体一般只放请求接口方法、请求的业务参数值和一些扩展属性。协议头是由一堆固定的长度参数组成,而协议体是根据请求接口和参数构造的,长度属于可变的,具体协议如下图所示:

定长协议:

1:Bit Offset——标识协议的其实位置

2:魔术位——标识是什么协议

3:整体长度——标识整个协议有多长,减去协议头长度就是协议体长度

4:头长度——标识协议头的长度,因为头是可扩展的,所以具体长度不固定,需要标识一下

5:协议版本——标识当前协议的版本,用于协议兼容性控制

6:消息类型——标识消息的类型,对于文本的需要,这里也需要嘛?协议类型可能是对象?可能是XML文件?可能是JSON码?正常来讲应该都是对象才对,让用于反序列化,猜测是为了扩展预留的

7:序列化方式——用于消息的序列化和反序列化

8:消息ID——用于表示请求和响应的关系

9:协议头扩展字段——用于扩展协议头,是协议具有扩展性,更加的灵活可控

10协议体——协议的内容,一堆堆的二进制数据,双方沟通的东西

协议头——规定信息转换的规则

协议体——信息真正的内容,由于在传输层对人不友好对应用程序也不友好需要转换一下

可扩展的协议

上面的协议为定长协议,那如果如果想在协议体重放一些扩展属性怎么办?

协议体里面是可以加新的参数,但这里有一个关键点,就是协议体里面的内容都是经过序列化出来的,也就是说你要获取到你参数的值,就必须把整个协议体里面的数据经过反序列化出来。但在某些场景下,这样做的代价有点高啊!

所以为了保证能平滑地升级改造前后的协议,我们有必要设计一种支持可扩展的协议。其关键在于让协议头支持可扩展,扩展后协议头的长度就不能定长了。

那要实现读取不定长的协议头里面的内容,在这之前肯定需要一个固定的地方读取长度,所以需要一个固定的写入协议头的长度。整体协议就变成了三部分内容:固定部分、协议头内容、协议体内容,前两部分我们还是可以统称为“协议头”,具体协议如下:

可扩展协议:

设计一个简单的 RPC 协议并不难,难的就是怎么去设计一个可“升级”的协议。不仅要让我们在扩展新特性的时候能做到向下兼容,而且要尽可能地减少资源损耗,所以我们协议的结构不仅要支持协议体的扩展,还要做到协议头也能扩展。

PS:

RPC 不直接用 HTTP 协议的一个原因是无法实现请求跟响应关联,每次请求都需要重新建立连接,响应完成后再关闭连接,所以我们要设计私有协议。那么在 RPC 里面,我们是怎么实现请求跟响应关联的呢?

一般RPC为了性能,会采用异步通信的方式,请求响应对应关联,就需要一个类似身份证号的ID,消息ID

RPC:设计可扩展且向后兼容的协议的更多相关文章

  1. 轻量级RPC设计与实现第五版(最终版)

    在最近一段时间里,通过搜集有关资料加上自己的理解,设计了一款轻量级RPC,起了一个名字lightWeightRPC.它拥有一个RPC常见的基本功能.主要功能和特点如下: 利用Spring实现依赖注入与 ...

  2. 指令集架构 x86-64 x86架构的64位拓展,向后兼容于16位及32位的x86架构

    https://zh.wikipedia.org/wiki/X86 x86泛指一系列英特尔公司用于开发处理器的指令集架构,这类处理器最早为1978年面市的"Intel 8086"C ...

  3. protobuf 向前兼容向后兼容

    http://blog.163.com/jiang_tao_2010/blog/static/12112689020114305013458/ 不错的protobuf.. protobuf的编码方式: ...

  4. 基于OAS设计可扩展OpenAPI

    前言 随着互联网行业的兴起,开发模式已逐步转换为微服务自治:小团队开发微服务,然后通过Restful接口相互调用.开发者们越来越渴望能够使用一种“官话”进行流畅的沟通,甚至实现多种编程语言系统的自动化 ...

  5. 【荐】说说CSS Hack 和向后兼容

    人一旦习惯了某些东西就很难去改,以及各种各样的原因,新的浏览器越来越多,而老的总淘汰不了.增长总是快于消亡导致了浏览器兼容是成了谈不完的话题.说 到浏览器兼容,CSS HACK自然而然地被我们想起.今 ...

  6. 实践javascript美术馆的小案例,学习到的东西还是蛮多的,包括javascript编程中的预留退路、分离javascript、以及实现向后兼容等

    javascript美术馆(改进2) 一.javascript编程过程中的好习惯 1.实现预留退路 js被禁掉,图片也可以显示出来,href属性带有图片路径 <script src=" ...

  7. 《javascript dom编程艺术》笔记(一)——优雅降级、向后兼容、多个函数绑定onload函数

    刚刚开始自学前端,如果不对请指正:欢迎各位技术大牛指点. 开始学习<javascript dom编程艺术>,整理一下学习到的知识.今天刚刚看到第六章,记下get到的几个知识点. 优雅降级 ...

  8. 采用truelicense进行Java规划license控制 扩展可以验证后,license 开始结束日期,验证绑定一个给定的mac住址

    采用truelicense进行Java规划license控制 扩展可以验证后,license 开始结束日期,验证绑定一个给定的mac住址. Truelicense 它是一个开源java license ...

  9. html与JacaScript中的重要思想:预留后路、向后兼容、js分离

    以一个简单的web程序为例 详细设计模式请配合代码及凝视食用 <!DOCTYPE html> <!-- 1 预留退路:假设用户禁用了js.链接还能正常显示吗?(href) 2 分离j ...

  10. 系统扩展与 macOS 不兼容

    系统扩展与 macOS 不兼容 某些系统扩展与当前版本的 macOS 不兼容或将与后续 macOS 版本不兼容 https://support.apple.com/zh-cn/HT210999 ref ...

随机推荐

  1. 傻妞教程——对接QQ机器人go-cqhttp

    原本我懒,用的傻妞QQbot一键安装版,docker的,最近有点问题,索性换了go-cqhttp 安装go-cqhttp: go-cqhttp项目地址:https://github.com/Mrs4s ...

  2. redis - [07] 数据类型

      redis是一个开源(BSD许可)的,内存中的数据结构存储系统,可以用作数据库.缓存和消息中间件MQ.它支持多种类型的数据结构,如字符串(String).散列(Hash).列表(List).集合( ...

  3. springboot 中 java.util.zip 文件的压缩

    package com.geotmt.billingcenter.common.utils; import org.datanucleus.util.StringUtils; import org.s ...

  4. DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?

    大家好,我是狂师. 前阵子在知乎闲逛时,有个问题激起了大家的热议:"DeepSeek 如何颠覆传统软件测试?测试工程师会被淘汰吗".这看似简单的一问,激起层层思考,针对这个问题,今 ...

  5. ubuntu 22.04 deskop 无法打开terminal

    系统语言设置的问题,改为汉语即可

  6. 【高德地图】离线地图jsapi2.0 插件引用

    当引用一个插件,但是这个插件没有时会报错: Uncaught TypeError: AMap.ControlBar is not a constructor 这里引用了罗盘这个插件 这时候需要翻看高德 ...

  7. 【Azure Fabric Service】分享使用Visual Studio 2022发布中国区Service Fabric服务应用的办法

    问题描述 使用Visual Studio 2022如何发布Service Fabric到中国区云服务呢? 因为使用VS2022中的插件无法创建Service Fabric Cluster服务. 那么, ...

  8. 【消息利器RabbitMQ】RabbitMQ常用内容浅析

    以下是一篇关于 RabbitMQ 的博客内容,涵盖了从基础到死信队列的实现,以及 RabbitMQ 其他常用知识点的补充.内容逻辑清晰,代码完整,适合直接发布. 使用 RabbitMQ 实现消息队列与 ...

  9. 标准javabean

    1.javabean介绍 javabean,名为实体类,封装数据的类 前面我们写的类都是实体类,但我们写的不是标准的实体类 . 2.标准的javabean写法 如图 3.快捷键 一个成员变量就要写两个 ...

  10. 牛客小白月赛104 C-小红打怪

    小红打怪 答案有单调性,使用二分答案来做 但是当时没有想到用二分,而是卡在怎么处理这三种攻击了. 可以把进行x回合的攻击,分为先进行x回合的全体打击,再进行x回合的范围打击,最后验证剩余血量够不够x回 ...