一、Protobuf 简介
1.1 Protobuf 是什么
Protocol Buffers (简称 Protobuf )是 Google 公司开源的一种轻便高效的结构化数据存储格式,用于序列化和反序列化结构化数据的代码生成器。它可以用于通讯协议和数据存储等领域。
Protobuf 是以 .proto 文件形式定义结构化数据的方式和格式。并且通过代码生成器生成各平台(C/C++、Java、Python、Go 等)的数据访问类,这些生成的类可以用来在对应的语言中解析、序列化 Protobuf 数据。
1.2 Protobuf 的优点
Protobuf 作为一种数据格式和工具,有以下优点:
1、性能高,序列化和反序列化速度很快
Protobuf 采用二进制格式存储数据,相比 XML 和 JSON 等格式,可以大幅减少数据体积, serialization 和 deserialization 的性能也更优。这对于高性能场景非常有利。
2、跨平台,多语言支持广泛
Protobuf 提供了标准的 .proto 文件格式和数据描述语法,然后可以通过 protoc 工具,自动生成各主流语言的数据访问类,如 C/C++、Java、Python、Go 等。
这保证了在不同平台和不同语言 scenarios 下,可以解析和验证一致的 Protobuf 数据。
3、定义结构化数据格式,方便维护升级
通过 .proto 文件定义数据格式,可以清晰界定不同版本数据格式的兼容关系,格式修改后也方便使用旧格式数据。
4、数据体积小,便于存储和传输
相比 XML、JSON,Protobuf 的二进制编码可以大幅减小数据体积,节省存储和网络传输成本。
5、扩展性好,灵活支持新增字段
通过定义可选和 Required 字段,可以轻松添加和删除消息中的字段,而不影响已有字段的序号,便于数据格式的扩展和演进。
二、Go 语言中使用 Protobuf
2.1 在 Go 语言中安装 Protobuf 库
在 Go 语言中使用 Protobuf 主要依赖 google 开源的 golang/protobuf 库,使用以下命令安装:
|
|
安装完成后,在代码中 import 此库:
|
|
2.2 使用 protoc 编译.proto 文件
编写 .proto 文件后,需要使用 protoc 命令生成 Go 代码,例如:
|
|
这会根据 message.proto 中的消息定义,生成 Go 语言版本的访问类,存放在 message.pb.go 文件中。
2.3 Protobuf 消息的编码和解码
golang/protobuf 库中主要包含下面两个函数,用来序列化和反序列化 Protobuf 消息:
|
|
其中 Message 是一个满足 protobuf.Message 接口的 Protobuf 消息对象,可以是通过 .proto 生成的 pb.go 文件中定义的类型,也可以是动态消息。
2.4 Protobuf 服务的定义
除了用于数据存储、网络通信外,Protobuf 也可以用来定义服务接口(RPC 服务)。语法如下:
|
|
这样就定义了一个 RPC 服务 interface,包含一个 Search 方法。然后客户端和服务器端通过实现这个 interface,来发送、处理服务请求和响应。
服务端需要实现服务接口定义的方法,客户端需要调用这个接口方法,传递请求参数,获取响应结果。
这两个函数可以方便的在任意 Go 类型与 Protobuf 二进制格式之间进行转换。
三、Protobuf 消息的定义
通过 .proto 文件, 可定义 Protobuf 中的消息结构。Protobuf 消息由一系列字段组成,使用 message 定义,每个消息可包含多种类型的字段。
3.1 3.1 消息类型
Protobuf 支持标量类型、复合类型的消息定义。
- 标量类型: 包括整型、浮点型、布尔型、字符串等;
- 复合类型: 主要是其它消息类型,一个消息字段可以引用其它消息类型。
3.2 标量类型
语法格式如下:
|
|
常用标量类型和修饰符总结如下:
- int32,int64 - 有符号整型
- uint32,uint64 - 无符号整型
- bool - 布尔类型
- string - 字符串类型
- bytes - 字节数组
- float,double - 浮点类型
- repeated - 重复类型,表示数组
- required - 必填字段
- optional - 可选字段,默认值
示例:
|
|
这定义了一个 Person
消息,包含必填的 name
、age
字段和可选的 email
字段。
使用 protobuf 可定义一对请求和响应消息,用于 RPC 服务接口的输入和输出参数。
|
|
这样通过一对请求响应消息消息,定义了服务接口的入参和返回值格式。
3.3 import 公共 proto 文件
为了重用消息定义和其它 .proto 文件的内容,可以用 import 语句导入其它 .proto 文件。
例如:
|
|
这样就可以直接引用 other.proto 中定义的消息、枚举等。
3.4 使用 options 设置项
Protobuf 支持自定义 options 字段,对消息、枚举进行注解或设置生成参数:
|
|
这为 text 字段添加一个自定义 option 注解。
四、Go 语言 Protobuf 实践
下面以一个完整的例子,演示下 Go 语言中使用 Protobuf 的整个流程。
4.1 定义 Protobuf 消息
编写一个 person.proto 文件,定义 Person 消息格式:
|
|
4.2 生成 Go 代码
然后使用 protoc 命令根据 person.proto 生成 Go 语言代码:
|
|
这会生成一个 person.pb.go 文件。
4.3 序列化和反序列化 Protobuf 消息
有了生成的 Go 访问类, 就可很方便的在 Go 代码中处理 Person 消息。
例如序列化和反序列化:
|
|
4.4 Protobuf 服务端和客户端
可利用 Protobuf 来定义服务接口,下面演示服务器和客户端的实现。
|
|
这定义了一个 PersonService,包含获取 Person 的 GetPerson 方法。
在服务器代码中实现这个接口:
|
|
在客户端, 可调用这个接口:
|
|
这样通过 gRPC 框架,就可以访问服务器定义的 Protobuf 服务。
4.5 Protobuf 与 gRPC 集成
Protobuf 定义的消息和服务可以很容易的在 gRPC 框架中使用,gRPC 正是通过 Protobuf 接口定义实现服务通信的。
服务器端实现 Protobuf 接口,客户端调用接口,二者通过 gRPC 通讯。
五、Protobuf 使用注意事项和经验
5.1 版本控制
为了兼容旧版本,在修改消息定义时,应该谨慎地创建新字段而不是删除旧字段。
5.2 向后兼容
- 对于 int32、uint32、int64、uint64、bool、string、bytes 字段,新代码可以读写旧消息,前向后兼容性是没有问题的。
- 对于 repeated 字段,删除或者顺序改变字段号,会造成不兼容。
- 新增 optional 或 repeated 字段,前向后兼容性是没有问题。但是新增 required 字段则会造成解析问题。
所以新增字段时,应使用 optional 而不是 required。
5.3 包含大数据量字段
由于 Protobuf 是二进制编码的,如果有字段包含非常大的数据(如图片、视频),会大幅增加消息大小。
这时可以考虑通过指针引用独立文件的形式,避免消息体积过大。
5.4 Protobuf 优缺点
相比 XML 和 JSON 数据格式, Protobuf 作为一种高效的结构化数据存储和交换格式,具有以下优点:
- 编码效率高,序列化后数据体积小
- 解析速度快
- 支持数据格式升级与兼容
- 支持定义服务接口
- 跨平台跨语言,通过编译支持各平台访问
当然也存在一些限制,比如不适合处理频繁修改的数据格式,不支持数据查询等。所以 Protobuf 在很多性能敏感、跨平台的场景下,可以发挥很好的作用。
5.5 在 Go 语言项目中的作用
在 Go 语言中,Protobuf 可以用于:
- 定义项目中的数据结构体
- 网络服务的请求响应参数和结果
- RPC 服务接口定义
- 数据存储格式定义
通过 Protobuf 接口定义,可以实现服务端和客户端的松耦合。
并且利用 Protobuf 接口语言无关性,可以支持多语言访问后端 Go 服务,实现更好的语言融合。