吃芒果有什么坏处| 什么条什么理| 枸杞加红枣泡水喝有什么功效| 女性体毛多是什么原因| 二郎神是什么生肖| 蓟类植物是什么| 工资5k是什么意思| 贝兄念什么| hairy什么意思| lsa是什么意思| 黄痰是什么原因造成的| 牙齿黄是什么原因| 包皮嵌顿是什么| 什么是标准差| 鼻炎吃什么消炎药效果最好| 舅父是什么意思| oa是什么意思| 促排是什么意思| 什么叫幽门螺旋杆菌| 人体最大的排毒器官是什么| 正师级相当于地方什么级别| 李宇春父亲是干什么的| 乡政府属于什么单位| 中暑有什么症状| 刺猬为什么叫白仙| 腋下异味用什么药| 合成革是什么材质| 5月10日是什么星座| 望远镜10x50什么意思| 黄皮果是什么水果| 女人喝什么调节内分泌| 补钾吃什么| 女人排卵期什么时候| 慢性子宫颈炎是什么意思| 一什么边| 猫代表什么象征意义| 血热是什么原因引起的| 七月十六是什么日子| 老舍原名什么| 腰酸背痛是什么原因| ib是什么意思| 胃糜烂要吃什么药| 饱和度是什么意思| 起义是什么意思| 沈阳有什么特产| 西梅不能和什么一起吃| sob是什么意思| 挂帅是什么意思| 粥米是什么米| 韬光养晦是什么意思| 夜尿多吃什么药效果好| 董事长是什么职位| 什么是ct| 脂肪肝吃什么药好| 你是电你是光是什么歌| 4.28什么星座| 猴年马月什么意思| 主诉是什么意思| 为什么清真不吃猪肉| 人为什么会做梦| 梅毒螺旋体抗体阳性是什么意思| 腋臭和狐臭有什么区别| 车前草有什么功效和作用| 假花放在家里有什么忌讳| 一建什么时候报名| 双手麻木是什么原因| 乙型肝炎核心抗体阳性是什么意思| 蛋白质偏高是什么原因| 经常放臭屁是什么原因| 梦见牙齿掉了是什么征兆| 泌乳素是什么意思| 胃肠炎吃什么食物| 肌酸激酶偏高吃什么药| 吃完饭恶心想吐是什么原因| 大宗商品是什么意思| 肛裂是什么| 一什么鱼塘| 县级干部是什么级别| 什么头十足| 孕妇胃疼可以吃什么药| 是谁送你来到我身边是什么歌| 甲减检查什么项目| 什么是流程| 减肥早餐吃什么好| 海豹油有什么功效| 红豆杉是什么植物| 春秋鼎盛是什么意思| 如字五行属什么| 阴唇肥大有什么影响| 急性青光眼是什么症状| 7月1日是什么星座| 扶他是什么意思| 梦见屎是什么意思| 孀居是什么意思| 甲功是什么意思| 盐酸莫西沙星主治什么| d2聚体高是什么意思| 肛裂用什么药膏| 小腹疼痛什么原因| 经常流眼泪是什么原因| 乳头内陷挂什么科| 小排畸主要检查什么| 经常做噩梦的原因是什么| 翘首以盼什么意思| 疣吃什么药能治好| 赤潮是什么意思| 淋巴细胞绝对值偏高是什么意思| 宜五行属什么| 乙肝表面抗原阴性是什么意思| 幽门螺旋杆菌阳性代表什么| col是什么的缩写| 哈尔滨市长什么级别| 藏毛窦是什么病| 干黄酱是什么酱| 株连九族是什么意思| 白细胞偏高是什么原因| 酒后喝什么解酒| 心跳不规律是什么原因| 3月19是什么星座| 丑小鸭告诉我们一个什么道理| 早上喝豆浆有什么好处| 眼花是什么原因| 脖子后面正中间有痣代表什么| 糖尿病的症状是什么| 房颤有什么危害| 腹胀吃什么药最有效| 量贩式ktv是什么意思| 胃不好可以吃什么水果| 空调综合征有什么症状| 牙疼吃什么药最管用| 男闺蜜是什么意思| jk是什么| 金牛男喜欢什么样的女生| 早上起来心慌是什么原因| 夏天适合吃什么菜| hpv59阳性是什么意思| 一直流鼻血是什么原因| 19属什么| 沙肝是什么| 传教士是什么姿势| 黄猫来家里有什么预兆| 刚需是什么意思| 心直口快是什么意思| 傍大款是什么意思| 老人适合喝什么茶| 海藻糖是什么糖| 塞肛门的止痛药叫什么| 什么饮料不含糖| 什么地赶来| 眼睛疼是什么原因| 梦见干活是什么意思| 中秋节送什么水果好| 女人什么时候绝经正常| 黑色上衣配什么颜色的裤子| 前列腺增生是什么意思| 口腔异味吃什么药| 为什么会长口腔溃疡的原因| 纳财是什么意思| 吃干饭是什么意思| 感染性腹泻吃什么药| 口腔溃疡用什么药治疗| 女生经常手淫有什么危害| 苯氧乙醇是什么| 巨蟹座男和什么座最配对| 胰腺做什么检查| 胆囊炎什么不能吃| 心绞痛是什么原因引起的| 天蝎座什么象星座| 经期吃什么补气血| 涧是什么意思| 儿童过敏性结膜炎用什么眼药水| 翡翠是什么玉| 卑微是什么意思| 新生儿白细胞高是什么原因| 被子植物是什么| 五味是什么| 乙肝核心抗体阳性说明什么| 加应子是什么水果| 鼻子痒用什么药| 舔是什么意思| 榄仁是什么| 性生活过多有什么危害| 什么是收缩压和舒张压| 阴道口出血是什么原因| 疣长什么样子| 尿液茶色是什么原因| 碗打碎了预示着什么| 本科毕业证是什么颜色| 四季豆是什么| 牙龈红肿是什么原因| 利润是什么| 南京有什么好玩的景点| 辣子鸡属于什么菜系| miles是什么意思| 为什么会得脑梗| 左后背疼是什么原因| 专项变应原筛查是什么| 舌根苔白厚腻是什么原因| 四月十号是什么星座| 鸡炖什么好吃| 帕金森吃什么药好得快| 什么是polo衫| 十月二十五是什么星座| 断肠草长什么样| 麻雀为什么跳着走| 1946年属什么| 人流后能吃什么水果| 下午五六点是什么时辰| 狗肉配什么菜好吃| spo2是什么意思| 早唐筛查是检查什么| cy什么意思| 食管反流用什么药效果好| 狗狗喝什么水| 逍遥丸主治什么病| 米线和米粉有什么区别| 鼻子流水是什么原因| 挚爱适合用在什么人| 异位妊娠是什么意思| 95是什么意思| 喝中药可以吃什么水果| 子宫内膜息肉吃什么药| dq是什么| 蓉字五行属什么| 天蝎座的幸运色是什么| 白带黄吃什么药| 脑震荡有什么症状| 肉桂和桂皮有什么区别| 梦见酒是什么意思| 心脏支架不能吃什么| 农历10月26日是什么星座| 缺钾吃什么食物| 什么是情劫| 雪莲果什么时候成熟| 实时播报什么意思| 画地为牢什么意思| 阳历7月份是什么星座| 手串断了寓意什么| 未时左眼跳是什么预兆| c3是什么意思| 左肾结晶是什么意思| 报价是什么意思| gi什么意思| 牙杀完神经为什么还疼| 梦魇是什么原因造成的| 肺腺瘤是什么| 关节痛去医院挂什么科| 谅解什么意思| 999足金是什么意思| 身上长痘痘是什么原因| 总胆固醇偏高吃什么药| 梦见水代表什么| 吃鸡蛋胃疼是什么原因| 什么的雄鸡| 朱红色是什么颜色| dic医学上是什么意思| 逆天是什么意思| 血脂挂什么科| 鱼油什么时间吃最好| 梦见鳄鱼是什么预兆| 什么杀精子最厉害| 小资生活是什么意思| 2月20日是什么星座| 牡蛎和生蚝有什么区别| 百度

默认
打赏 发表评论 0
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结
阅读(2793) | 评论(0 收藏1 淘帖1 1
微信扫一扫关注!

本文来自腾讯云开发者罗志赟分享,即时通讯网进行了排版优化和修订。


1、引言


Protobuf 是 Google 出品的序列化框架,可跨平台、跨语言使用,扩展性良好。与 XML、JSON 等序列化框架相同,Protobuf 广泛的应用于数据存储,网络传输,RPC 调用等环境。我们现在所有的协议、配置、数据库的表达都是以 Protobuf 来进行承载的,所以我想深入总结一下 Protobuf 这个协议,以免踩坑。

IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_cover_opti.png

2、系列文章


本文是系列文章中的第 11 篇,本系列总目录如下:


3、快速认识Protobuf  


导语简单的介绍了一下 Protobuf ,它具有很多优点,但也有一些需要注意的缺点。

优点:

  • 1)效率高:Protobuf 以二进制格式存储数据,比如 XML 和 JSON 等文本格式更紧凑,也更快。序列化和反序列化的速度也很快;
  • 2)跨语言支持:Protobuf 支持多种编程语言,包括 C++、Java、Python 等;
  • 3)清晰的结构定义:使用 Protobuf,可以清晰地定义数据的结构,这有助于维护和理解;
  • 4)向后兼容性:你可以添加或者删除字段,而不会破坏老的应用程序。这对于长期的项目来说是非常有价值的。

缺点:

  • 1)不直观:由于 Protobuf 是二进制格式,人不能直接阅读和修改它。这对于调试和测试来说可能会有些困难;
  • 2)缺乏一些数据类型:例如没有内建的日期、时间类型,对于这些类型的数据,需要手动转换成可以支持的类型,如 string 或 int;
  • 3)需要额外的编译步骤:你需要先定义数据结构,然后使用 Protobuf 的编译器将其编译成目标语言的代码,这是一个额外的步骤,可能会影响开发流程。

总的来说:Protobuf 是一个强大而高效的数据序列化工具,我们一方面看重它的性能以及兼容性,除此之外就是它强制要求清晰的定义出来,以文件的形式呈现出来方便我们维护管理。下面我们主要看它的编码原理,以及在使用上有什么需要注意的地方。

4、编码原理概述


对于 Protobuf 它的编码是很紧凑的,我们先看一下 message 的结构。

举一个简单的例子:
message Student {
string name = 1;
int32 age = 2; 
}

message 是一系列键值对,编码过之后实际上只有 tag 序列号和对应的值,这一点相比我们熟悉的 json 很不一样。

所以对于 protobuf 来说没有 .proto 文件是无法解出来的:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_1.png

对于 tag 来说,它保存了 message 字段的编号以及类型信息,我们可以做个实验,把 name 这个 tag 编码后的二进制打印出来。

示例代码如下:
funcmain() {
  student := student.Student{}
  student.Name = "t"
  marshal, _ := proto.Marshal(&student)
  fmt.Println(fmt.Sprintf("%08b", marshal)) // 00001010 00000001 01110100
}

打印出来的结果是这样:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_2.png

上图中:由于 name 是 string 类型,所以第一个 byte 是tag,第二 byte 是 string 的长度,第三个 byte 是值,也就是我们上面设置的 “t”。

我们下面先看看 tag:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_3.png

tag 里面会包含两部分信息:字段序号,字段类型,计算方式就是上图的公式。上图中将 name 这个字段序列化成二进制我们可以看到,第一个 bit 是标记位,表示是否字段结尾,这里是0表示 tag 已结尾,tag 占用1byte;接下来 4 个 bit 表示的是字段序号,所以范围 1 到 15 中的字段编号只需要 1 bit进行编码,我们可以做个实验看看,将 tag 改成16(如下图所示)。

IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_4.png

由上图所示:每个 byte 第一个bit表示是否结束,0表示结束,所以上面 tag 用两个 byte 表示,并且 protobuf 是小端编码的,需要转成大端方便阅读,所以我们可以知道 tag 去掉每个 byte 第一个 bit 之后,后三位表示类型,是3,其余位是编号表示 16。

所以从上面编码规则我们也可以知道,字段尽可能精简一些,字段尽量不要超过 16 个,这样就可以用一个 byte 表示了。

同时我们也可以知道:protobuf 序列化是不带字段名的,所以如果客户端的 proto 文件只修改了字段名,请求服务端是安全的,服务端继续用根据序列编号还是解出来原来的字段。但是需要注意的是不要修改字段类型。

接下来我们看看类型,protobuf 共定义了 6 种类型,其中两种是废弃的:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_5.png

上面的例子中,Name 是 string 类型所以上面 tag 类型解出来是 010 ,也就是 2。

5、Varints编码技术原理


对于 protobuf 来说对数字类型做了压缩的,普通情况下一个 int32 类型需要 4 byte,而 protobuf 表示127以内的数字只需要 2 byte。因为对于一个普通 int32 类型数字,如果数字很小,那么实际上有效位很少,比如要表示 1 这个数字,二进制可能是这样。
0000000000000000 00000000 00000001

前 3 个字节都是 0 没有表示任何信息,protobuf 就是将这些 0 都去除了,用 1 byte 表示 1 这个数字,再用 1 byte 表示 tag 的编号和类型,所以占用了 2byte。

比如我们对上面 student 设置 age 等于 150:
funcmain() {
  student := student.Student{}
  student.Age = 150
  marshal, _ := proto.Marshal(&student)
  fmt.Println(fmt.Sprintf("%08b", marshal)) //00010000 10010110 00000001
  fmt.Println(fmt.Sprintf("%08b", "a"))
}

上面打印出来的二进制如下,因为 150 超过 127,所以需要用两个 byte 表示:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_6.png

第一个 byte 是 tag 这里就不再重复介绍了。后面两个 byte 是真实的值,每个 byte 的最高位 bit 是标记位,表示是否结束。然后我们转换成大端表示,串联起来就可以得到它的值是 150。

6、ZigZag 编码


Varints 编码之所以可缩短数字所占的存储字节数是因为去掉了 0 ,但是对于负数来说就不行了,因为负数的符号位为 1,并且对于32 位的有符号数都会转换成 64 位无符号来处理。

例如 -1,用 Varints 编码之后的二进制:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_7.png

所以 Varints 编码负数总共会恒定占用 11 byte,tag 一个byte,值占用 10 byte。

为此 Google Protocol Buffer 定义了 sint32 这种类型,采用 zigzag 编码。将所有整数映射成无符号整数,然后再采用 varint 编码方式编码。

例如:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_8.png

照上面的表,也就是将 -1 编码成 1,将 1 编码成 2,全部都做映射。

实际的 Zigzag 映射函数为:
(n << 1) ^ (n >> 31)  //for 32 bit 
(n << 1) ^ (n >> 63)  //for 64 bit

对于使用来说,只是编码方式变了,使用是不受影响,所以对于如果有很高比例负数的数据,可以尝试使用 sint 类型,节省一些空间。

7、 embedded messages & repeated


比如现在定义这样的 proto:
message Lecture {
int32 price =1 ; 
}
message Student {
repeated int32 scores = 1;
Lecture lecture = 2;
}

给 scores 取值为 [1,2,3],编码之后发现其实和上面讲的 string 类型很像。第一个 byte 是 tag;第二 byte 是 len,长度为 3;后面三个 byte 都是值,我们设定的 1,2,3。

下面是只设置了 scores 的编码图:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_9.png

再来看看 embedded messages 类型,让 Lecture 的 price 设置为150 好了。

下面是只设置了 Lecture 的编码图:
IM通讯协议专题学习(十一):IM流量倍降的秘密——快速理解Protobuf和踩坑总结_10.png

其实结构也很简单,左边的是 Student 类型,右边是 Lecture 类型。有点不同的是对于 embedded messages 会将大小计算出来。

8、 踩坑总结和最佳实践


8.1字段编号


需要注意的是范围 1 到 15 中的字段编号需要一个字节进行编码,包括字段编号和字段类型;范围 16 至 2047 中的字段编号需要两个字节。所以你应该保留数字 1 到 15 作为非常频繁出现的消息元素。

因为 Protobuf 的第一个 byte 是用来判断是否结尾,所以单字节表示序列号的时候最高位是零,而最低三位表示类型,所以只剩下 4 位可用了。也就是说,当你的字段数量超过 16 时,就需要用两个以上的字节表示了。

8.2保留字段


一般的情况下,我们是不会轻易的删除字段的,防止客户端和服务端出现协议不一致的情况,如果您通过完全删除某个字段或将其注释掉来更新消息类型,那么未来的其他人不知道这个 tag 或字段被删除过了,我们可以使用 reserved 来标记被删除的字段。

如:
message Foo {
reserved2, 15, 9 to 11;
reserved"foo", "bar";
}

当然除了 message ,reserved也可以在枚举类型中使用。

8.3不要修改字段 tag 编号以及字段类型


Protobuf 序列化是不带字段名的,所以如果客户端的 proto 文件只修改了字段名,请求服务端是安全的,服务端继续用根据序列编号还是解出来原来的字段。

但是需要注意的是不要修改字段类型,以及序列编号,修改了之后就可能按照编号找错类型。

8.4不要使用 required 关键字


required 意味着消息中必须包含这个字段,并且字段的值必须被设置。如果在序列化或者反序列化的过程中,该字段没有被设置,那么 protobuf 库就会抛出一个错误。

如果你在初期定义了一个 required 字段,但是在后来的版本中你想要删除它,那么这就会造成问题,因为旧的代码会期待该字段始终存在。为了确保兼容性,Google 在最新版本的 Protobuf(protobuf 3)中已经不再支持 required 修饰符。

8.5尽量使用小整数


Varints 编码表示127以内的数字只需要 2 byte,1 byte 是 tag,1 byte 是值,压缩效果很好。但是如果表示一个很大的数如 :1<<31 - 1,除去 tag 外需要占用 5 byte,比普通的 int 32 多1 byte,因为 protobuf 每个byte最高位有一个标识符占用 1 bit。

8.6需要传输负数,试试 sint32 或 sint64


因为负数的符号位为 1,并且 Varints 编码对于负数如果是32位的有符号数都会转换成64位无符号来处理,所以 Varints 编码负数总共会恒定占用11 byte,tag 一个 byte,值占用 10 byte。

而 sint32 和 sint64 将所有整数映射成无符号整数,然后再采用 varint 编码方式编码,如果数字比较还是可以节省一定的空间的。如果文章对你有帮助,欢迎转发分享~

9、 参考资料


[1] 一个基于Protocol Buffer的Java代码演示
[2] 如何选择即时通讯应用的数据传输格式
[3] 强列建议将Protobuf作为你的即时通讯应用数据传输格式
[4] 移动端IM开发需要面对的技术问题(含通信协议选择)
[5] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端
[6] 理论联系实际:一套典型的IM通信协议设计详解
[7] 58到家实时消息系统的协议设计等技术实践分享
[8] 技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解
[9] APP与后台通信数据格式的演进:从文本协议到二进制协议
[10] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等
[11] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[12] 从新手到专家:如何设计一套亿级消息量的分布式IM系统
[13] 微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗
[14] 支持百万人超大群聊的Web端IM架构设计与实践
[15] 一年撸完百万行代码,企业微信的全新鸿蒙NEXT客户端架构演进之路

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:Web端实时通信技术SSE在携程机票业务中的实践应用下一篇:零基础音视频入门:你所不知道的Web前端音视频知识

本帖已收录至以下技术专辑

推荐方案
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部
头发麻是什么原因 淋巴滤泡形成什么意思 喝酒拉肚子是什么原因 脱臼是指什么从什么中滑脱 出cos是什么意思
cheblo空调是什么牌子 芃字五行属什么 瞬息万变什么意思 采是什么意思 厚颜无耻是什么意思
白糖和冰糖有什么区别 蛇的眼睛是什么颜色 红豆和赤小豆有什么区别 49是什么意思 死了是什么感觉
女性长胡子是什么原因 切除一侧输卵管对女性有什么影响 药学是干什么的 枫叶是什么颜色 为什么怀孕这么难
白细胞偏低吃什么helloaicloud.com 林黛玉和贾宝玉是什么关系beikeqingting.com 近字五行属什么hcv8jop5ns0r.cn 喝酒前喝什么不容易醉hcv8jop6ns4r.cn www是什么网hcv7jop7ns4r.cn
梦到钓鱼是什么意思hanqikai.com 三餐两点什么意思hcv8jop9ns0r.cn 路虎为什么叫奇瑞路虎creativexi.com 芈月和秦始皇是什么关系hcv7jop9ns9r.cn 维生素d是什么hcv9jop5ns6r.cn
白切鸡用什么鸡hcv9jop0ns1r.cn 艾字五行属什么hcv9jop1ns3r.cn 矢量是什么意思hcv9jop5ns1r.cn 日本全称是什么hcv8jop4ns2r.cn 什么是神话故事hcv7jop6ns9r.cn
没有料酒可以用什么代替hcv9jop3ns8r.cn 美尼尔氏综合症是什么病hcv9jop7ns0r.cn 蟾蜍是什么hcv9jop1ns9r.cn 一边脸大一边脸小是什么原因hcv7jop9ns2r.cn 精液是什么味hcv9jop2ns7r.cn
百度