写点什么

了解一下 ProtoBuf

  • 2022 年 2 月 21 日
  • 本文字数:2383 字

    阅读完需:约 8 分钟

序列化与反序列化

我们在进行网络通信调用的时候,总是需要将内存的数据块经过序列化,转换成为一种可以通过网络流进行传输的格式。而这种格式在经过了传输之后再经过序列化,能还原成我们预想中的数据结构。


那么我们对于这种用于中间网络传输的数据格式就有一定的要求。首先它可以准确地描述数据内容,在此基础上我们则希望它尽量的小。


最开始流行起来的是 XML,可扩展标记语言。由于它可以用来标记数据、定义数据类型,所以用户可以自己定义数据自己的语言,从而让对不同的数据结构化成统一的格式称为了可能。


而另外一个我们熟知的则是 JSON(JavaScript Object Notation, JS 对象简谱)。尽管 JSON 中缺少了 XML 中的标签属性等描述方式,但是足够简介和清晰的层次结构使得其成为了必 XML 更受欢迎的数据交换格式。


同一份数据显然 JSON 的数据量比 XML 所使用的空间更少。那么空间省略在哪里呢?一方面是 json 使用更简单的字符来定义数据间的关联关系;另一方面是 JSON 减少了对数据类型的描述。但是丢少的数据类型再哪里呢?


以 Java 中的 OpenFeign 举例,JSON 中缺少的类型定义被定义道程序中的接口中了。当进行序列化与反序列化时,JSON 格式并不记录数据的类型,具体的数据类型在序列化方与反序列化方通过事先约定的接口来进行定义。这样就减少了信息传输过程中的信息量,从而让数据得以压缩。


但是 JSON 由于没有定义数据类型,所以在传输的过程中实际上就都是文本流,那么这种方法还可以进一步压缩吗?

ProtoBuf 的原理概要

结合上文的讨论,我们先说结论:方法是有的,并写当前的实现方式是 ProtoBuf。但在此之前我们先来了解一下 ProtoBuf。


我们可以先看看官方给出的定义与描述:


protocol buffers 是一种与语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10 倍)、更快(20 ~ 100 倍)、更为简单。你可以定义数据的结构,然后使用特殊生成的源代码轻松地在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。


同样的,ProtoBuf 也是一种支持序列化反序列化的方法,并且他具有很多优点:


  • 多语言

  • 多平台

  • 体积小

  • 扩展性好


实际上,ProtoBuf 提供了一种通用的数据描述方式,这种定义数据的方式是通用的,就如同 JSON 或者 XML 一样。


接下来我们来来回答本节一开始的问题,针对 JSON 来说,ProtoBuf 是如何将体积变得更小的呢?答案很简单,就是为数据序列化反序列化提供更多的先验知识。


本文暂不过度深入 ProtoBuf 原理,但是可以通过一张图来进行简要说明(图片来自网络):


ProtoBuf 中的数据是按顺序进行排列,而整体的结构为若干个 field,每一个 field 中由 Tag-[Length]-Value 组成。Length 是可选的,而是否存在 Length 是通过 Tag 的类型来决定的。也就是说如果是指定的类型,比如 int64,那我们就可以知道 Value 的长度,也就不用在依靠 Length 来对其空间进行描述(redis 中的压缩列表也是这个思想)。


那么 field 应该对应的是什么字段呢?这个则是在序列化与反序列化时在 ProtoBuf 的服务端与客户端之间进行预先定义的。而因为提前定义了 field 的类型、排序,所以 field 本身可以不用对字段名、字段位置进行描述,只需要根据字段类型选用合适的二进制序列化方法,将字段本身的 value 值进行序列化传输即可。


稍微总结一下:


ProtoBuf 通过对传输字段的名称、顺序进行预定义,从而在传输结构中只需要顺序的记录每个字段的类型标签和二进制值。

二进制序列化

尽管上文和官方中都是以 XML 或者 JSON 来对 ProtoBuf 进行对比。但是因为 ProtoBuf 本身就是二进制序列化方式,所以从压缩比上比较感觉有点欺负人。


对应的在 Java 中二进制常用的序列化器有 Kryo 和 Hessian。但事实上,由于 Kryo 和 Hessian 中都需要对 Java 类名和字段信息进行存储。而 ProtoBuf 则只有 Tag-Length-Value 的数据对,且 Value 更是有针对性的特殊编码,所以空间占用小的很多。


Kryo 是专门针对 Java 进行优化了的。所以在使用的便捷性上来说 Kryo 则更加方便。但 ProtoBuf 是跨平台的,且由于进行了字段的顺序定义,所以似的 ProtoBuf 定义后的接口是可以向前兼容的(只向后追加字段),而这种优势是 Kryo 所没有的。

使用 ProtoBuf

ProtoBuf 是跨语言的,使用 ProtoBuf 的第一步是先定一个 proto 文件,而由于 ProtoBuf 2 和 3 语言版本的不同,其定义格式会有所不同,具体的细节还是得参考官方文档:https://developers.google.cn/protocol-buffers/docs/proto3


对于 ProtoBuf 3 的定义文档我们可以按如下方法定义:


syntax = "proto3";//指定版本为proto3,默认为proto2message SearchRequest {    string query = 1;    int32 page_number = 2;    repeated int32 result = 3;}
复制代码


其中 message 关键字是定义的文件名,而 string、int32 则是预定的字段类型,repeated 则是描述字段为可重复任意多次的字段。


ProtoBuf 通过这种形式的文件定义了传输信息的文件结构。


但是之前小节中我们知道了 ProtoBuf 是通过 Tag-[Length]-Value 组成的数据组来进行信息传输的,那么 proto 文件中定义的内容如何转换为实际传输的对象呢?


ProtoBuf 的做法是,为每一种语言提供一个生成器 protoc。通过使用 protoc 则可以根据.proto 文件生成为一组 java 文件。对应的官方语法演示样例为:


protoc --proto_path=src --java_out=build/gen src/foo.proto
复制代码


官方的生成参考为:https://developers.google.com/protocol-buffers/docs/reference/java-generated


生成后的 java 文件将提供对应的实体以及数据的构造方法等文件,从而支持后续的使用。


需要注意的是,ProtoBuf 是本质上是序列化方法,具体是通过 Spring Cloud 的 OpenFeign 进行接口调用,还是通过 grpc 进行接口调用,都是可以的。

最后

本文对 ProtoBuff 进行了概念的整理,并没有对每个细节都进行深入的梳理,可以当作概念科普来进行阅读。

发布于: 刚刚阅读数: 2
用户头像

人肉bug制造机 2020.06.26 加入

欢迎关注同名公众号!

评论

发布
暂无评论
了解一下ProtoBuf