Protobuf 总结

用途

Protobuf 是 google 出品的序列化框架,可跨平台、跨语言使用,扩展性良好。与 XML, JSON 等序列化框架相同,Protobuf 广泛的应用于数据存储,网络传输,RPC 调用等环境。

序列化: 将 数据结构或对象转换成二进制串的过程

反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

笔者认为序列化和反序列化可理解为上学的时候偷偷传的「纸条」,小 A 和 小 B 之间提前约定某种规则,小 A 按照规则写纸条的过程就是「序列化」,小 B 接到后按照规则翻译纸条上的内容的过程就是「反序列化」

序列化原理分析

优势

性能方面

  • 体积小:Protobuf 中使用了多种编码(Varint、Zigzag),序列化后,数据大小可缩小 3 倍
  • 传输速度快:带宽相同的情况下,体积小的传输速度更快
  • 序列化速度快:直接把对象和字节数据做转换

使用方面

  • 使用简单,维护成本低:仅需要维护一份 .proto 文件,protoc 编译器支持多种平台代码的生成

  • 向后兼容好:支持字段的增加和删除,笔者认为 json 亦可支持

  • 安全性:protobuf 编码后的数据比 json 编码的数据更难分析

  • 适用性: 不适用于对基于文本的标记文档(如 HTML)建模,但在传输数据量大 & 网络环境不稳定的数据存储、RPC 数据交换的场景下很适用

原理

  • Protocol Buffer 将消息里的每个字段进行编码后,再利用 T - L - V 方式进行数据存储。

    • Tag - Length - Value, 标识 - 长度 -字段值存储方式

      不需要分隔符就能分割字段,减少分割符使用

      采用 Varint & Zigzag 编码方式,存储空间利用率高

      没有设置字段值的字段,不需要编码(存储或传输过程中数据是完全不存在的),相应字段在解码的时候才会被设置默认值

  • Protocol Buffer 对于不同数据类型,采用不同的序列化方式(编码方式 & 数据存储方式)

    • Varint 编码方式

      变长编码方式,用字节表示数字,值越小的数字,使用越少的字节数表示,通过减少表示数字的字节数从而进行数据压缩。

      • 对于 int32 类型的数字,一般需要 4 个字节表示,若使用 Varint 编码,对于很小的 int32 类型数字,可以用 1 个字节表示,但很大的数字需要 5 个字节表示。
      • 在计算机内,负数的符号位为数字的最高位,会被计算机理解为很大的整数,一定需要 5 个 byte,所以 protobuf 中又引入了 Zigzag 编码。
    • Zigzag 编码方式

      变长编码方式,使用无符号数来表示有符号数字,使得绝对值小的数字都可以采用比较少子节来表示。特别是对表示负数的数据能更好地进行数据压缩

  • Ptotocol Buffer 对于数据编码方式T - L -V 数据存储方式 ,使得序列化后体积更小

使用建议

  • 字段标识号(Field_Number)尽量使用 1-15,且不要跳动使用

    tag 里的 Field_Number 字段是需要占用字节空间的。如果 Field_Number 大于 16, Field_Number 的编码就会占用 2 个字节, 那么 Tag 在编码时也就会占用更多的字节。

  • 若需要使用的字段值出现负数,优先使用 sint32/sint64

    采用 sint32/ sint64 数据类型表示负数时,会优先使用 Zigzag 编码再采用 Varint 编码,更加有效的压缩数据。

测试结果分析

在分析过原理之后,深入思考下 protobuf 是不是在任何使用场景下都是合适的?可以考虑如下几种场景:

  • 如果字段大部分都是字符串,占到决定性因素应该是字符串拷贝速度,而不是解析速度。
  • 影响解析速度的决定性因素是分支的数量,上述建议字段标识号不要超过 15。因为分支的存在,解析仍然是一个串行的过程。
  • 理论和实践并不一定完全保存一致。Protobuf 是一个理论上更快的格式,但是实现它的库并不一定就更快。而是取决于优化做得好不好,比如是否有不必要内存分配或者重复读取。

网上整理的测评结果

测评结果整理自 Protobuf 性能到底有没有比 JSON 快 5 倍,整理的是 Jackson 和 Protobuf 的性能对比,详情可点击查看:

测评方式 测评结果(Protobuf vs Jackson)
整数解码 8.51 倍
整数编码 2.9 倍
double 解码 13.75 倍
double 编码 12.71 倍
1 个字段的对象解码 2.5 倍
5 个字段的对象解码 1.3 倍
10 个字段的对象解码 1.22 倍
1 个字段的对象编码 1.22 倍
5 个字段的对象编码 1.68 倍
10 个字段的对象编码 1.72 倍
整数列表解码 2.92 倍
整数列表编码 1.35 倍
对象列表解码 1.26 倍
对象列表编码 2.22 倍
double 数组解码 5.18 倍
double 数组编码 15.63 倍
长字符串解码 1.85 倍
长字符串编码 0.96 倍,Jackson 比 Protobuf 略快

Jackson 是 Java 程序中用的最多的 JSON 解析器,benchmark 中开启了 AfterBurner 的加速特性(笔者不懂 Java 不知道这是用来做什么的)

自测数据

本着实践的精神笔者用 golang 写了一版本 protobuf VS json VS CustomWay(自定义格式)编码解码的测试,测试结果如下:

  • protobuf 格式

    message Content {string identifier = 1;string resourceLocator = 2;string bucket = 3;int64 time = 4;
    }
    
  • json 格式

    type JContent struct {Identifier      string `json:"Identifier"`ResourceLocator string `json:"ResourceLocator,omitempty"`Bucket          string `json:"Bucket"`Time            int64  `json:"Time,omitempty"`
    }
    
  • 自定义格式

    field1 field2 field3 field4
    

    注意: 按照空格分隔符分割 4 个字段,编码为拼接 4 个字段,解码为分割 4 个字段

测试结果:

goos: darwin
goarch: amd64
pkg: protobuf/benchmark
BenchmarkMarshalByProtoBuf-4        10000000           181 ns/op
BenchmarkUnmarshalByProtoBuf-4      10000000           158 ns/op
BenchmarkMarshalByCustomWay-4       20000000            85.0 ns/op
BenchmarkUnmarshalByCustumWay-4     10000000           143 ns/op
BenchmarkMarshalByJson-4             2000000           844 ns/op
BenchmarkUnmarshalByJson-4           1000000          2520 ns/op
PASS
ok      protobuf/benchmark  12.141s

测试代码:

package testimport ("encoding/json""strings""testing"proto "github.com/golang/protobuf/proto"
)var (f = []string{"shanghai","master","shanghai/chongming","1541388122",}content = Content{Identifier:      "shanghai",ResourceLocator: "shanghai/chongming",Bucket:          "master",Time:            1541388122,}jContent = JContent{Identifier:      "shanghai",ResourceLocator: "shanghai/chongming",Bucket:          "master",Time:            1541388122,}
)type customWay stringfunc (s *customWay) Marshal(fields []string) {s1 := strings.Join(fields, " ")*s = customWay(s1)
}func (s *customWay) Unmarshal() []string {return strings.Split(string(*s), " ")
}type JContent struct {Identifier      string `json:"Identifier"`ResourceLocator string `json:"ResourceLocator,omitempty"`Bucket          string `json:"Bucket"`Time            int64  `json:"Time,omitempty"`
}func BenchmarkMarshalByProtoBuf(b *testing.B) {for i := 0; i < b.N; i++ {proto.Marshal(&content)}
}
func BenchmarkUnmarshalByProtoBuf(b *testing.B) {bytes, _ := proto.Marshal(&content)result := Content{}b.ResetTimer()for i := 0; i < b.N; i++ {proto.Unmarshal(bytes, &result)}
}func BenchmarkMarshalByCustomWay(b *testing.B) {var sc customWayfor i := 0; i < b.N; i++ {sc.Marshal(f)}
}func BenchmarkUnmarshalByCustumWay(b *testing.B) {var sc customWaysc.Marshal(f)b.ResetTimer()for i := 0; i < b.N; i++ {sc.Unmarshal()}
}func BenchmarkMarshalByJson(b *testing.B) {for i := 0; i < b.N; i++ {json.Marshal(jContent)}
}
func BenchmarkUnmarshalByJson(b *testing.B) {bytes, _ := json.Marshal(jContent)result := JContent{}b.ResetTimer()for i := 0; i < b.N; i++ {json.Unmarshal(bytes, &result)}
}

Proto3 区别于 Proto2 的使用

  • 在第一行非空非注释行,必须写:syntax = "proto3";
  • 字段规则移除 「required」,并把 「optional」改为 「singular」
  • 「repeated」字段默认使用 paced 编码
  • 移除 default 选项
  • 枚举类型的第一个字段必须要为 0
  • 移除对扩展的支持,新增 Any 类型, Any 类型是用来替代 proto2 中的扩展的
  • 增加了 JSON 映射特性

参考资料

  • 几种 Go 序列化库的性能比较
  • Protobuf 性能到底有没有比 JSON 快 5 倍
  • Protobuf 的 proto3 与 proto2 的区别
  • Protocol Buffer 序列化原理大揭秘-为什么Protocol Buffer性能这么好?

正确使用 protobuf 的姿势相关推荐

  1. [摄影初学]正确的相机握持姿势

    [摄影初学]正确的相机握持姿势(图) 相机握持方法得当将有助于拍出更为清晰的照片,所以没有十分必要时,不要潇洒地单手拿着相机拍照,更不能心不在焉地随手捏着相机拍照.比较稳妥的方法是以双手平衡地握紧相机 ...

  2. linux关机shutdown无效,Linux正确shutdown关机的姿势

    相信接触过Linux的朋友都知道要让linux系统进行关机的操作就必须输入"shutdown"命令才可以,但有时候我们会遇到"Linux shutdown命令无效,返回提 ...

  3. 安卓强制横屏的坑!正确设置横屏的姿势!

    今天写一个页面的时候发现 progressdialog不停的报错,后来发现是onCreate了两次,导致第一次pd显示后activity重建导致报错,为会么会两次呢,想到activity使用了强制横屏 ...

  4. 会计用计算机正确手指摆放,打字姿势手位 打字手在键盘上正确放置的位置

    05 常见错误: 1.觉得正规姿势太别扭,只用一根手指按键,玩"一指禅",其实正确的姿势一般只要坚持两三天就可以适应,也是学习打字最基本的要求. 2.不是击键,而是按键,手指一直压 ...

  5. Switch正确使用enum的姿势

    错误姿势: private void TestEnum(ColorType type){switch (type){case ColorType.GREEN:break;case ColorType. ...

  6. 别在 Java 代码里乱打日志了,这才是正确的日志打印姿势!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 转自:公众号   石杉的架构笔记  作者:Lrwin 打日志的正确 ...

  7. 别在 Java 代码里乱打日志了,这才是正确的打日志姿势

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:lrwin 使用slf4j 使用门面模式的日志框架,有利于维 ...

  8. 别在 Java 代码里乱打日志了,这才是正确的打日志姿势!

    点击上方"方志朋",选择"置顶公众号" 技术文章第一时间送达! 来源:http://t.cn/E9BkD7a 使用slf4j 使用门面模式的日志框架,有利于维护 ...

  9. 别在Java代码里乱打日志了,这才是正确的打日志姿势!

    原文链接:http://t.cn/E9BkD7a 使用slf4j 使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一. 实现方式统一使用: Logback框架 打日志的正确方式 什么时候应该 ...

最新文章

  1. codewars-random(2)
  2. 拿什么来衡量程序员的生产力?
  3. php网站后台密码加密,thinkphp 后台登陆密码加密传入密钥
  4. 根据实例类型反射操作数据库(简单通用表操作类)
  5. Linux下的MySQL安装及卸载
  6. 决策树 算法原理及代码
  7. Docker学习总结(38)——开发环境中使用docker run安装Redis再总结
  8. 大数据?这些你了解吗?------之基础知识篇
  9. 【廖雪峰官方网站/Java教程】多线程(2)
  10. Vert.x Web
  11. 学习shell的第三天
  12. 不同时区不同夏令时(夏时制)间转换
  13. Python 3 《dictionary》入门练习
  14. java基于springboot+vue的旅游心得分享攻略系统 elementui
  15. 用户登录 验证数据库
  16. 如何在知网下载硕士、博士论文PDF?
  17. requests爬取豆瓣前250部高分电影
  18. h5 vr效果_H5案例|通过VR展示的那些烧脑游戏
  19. HDU 6194:string string string
  20. ps快速将白底图片变为透明图片

热门文章

  1. 循环神经网络RNN与LSTM
  2. 台式安装nas系统_从0开始使用“矿渣”低成本打造家庭NAS,黑群晖系统安装(中)...
  3. 10免费图标生成器网页设计师
  4. latex中的求和符号,连乘符号等
  5. Java API访问HA方式的HDFS
  6. 怎么绕过论坛回复_可怕,GPT3论坛跟帖灌水一周无人发现!专挑热搜,秒秒钟长文...
  7. 微信集成监控Job状态的功能
  8. Oculus Rift S 399美元,真香
  9. Bootstrap-01 (前台开发框架)
  10. 【1.skynet网络剖析】