Protobuf知识总结
一、初识Protobuf
Protobuf 是 Google 开发的一种数据交换格式。是二进制序列化协议及消息管理数据,可以把它理解为一个更高效、更规范的 JSON 或 XML。
核心思想:先定义结构,再生成代码,最后使用。
二、Protobuf 存在的核心意义
Protobuf 的核心意义在于解决在跨平台、跨语言环境中,进行高效、可靠且可扩展的结构化数据序列化与通信的问题。
它可以被理解为一种:
更小、更快、更简单的 XML 或 JSON。
用于通信协议的现代化、高性能替代品。
它的出现主要是为了弥补传统数据格式在性能、体积和规范化方面的不足。
三、与 JSON/XML 的对比:为什么需要 Protobuf?
让我们通过一个表格来直观对比:
特性 | Protocol Buffers | JSON | XML |
---|---|---|---|
数据体积 | 非常小(二进制格式,无冗余字段名) | 大(文本格式,包含重复的字段名) | 非常大(文本格式,包含大量标签) |
序列化/反序列化速度 | 非常快(直接编码为二进制,无需解析字符串) | 慢(需要解析文本、字符串转换) | 很慢(结构复杂,解析开销大) |
可读性 | 差(二进制格式,人眼不可读) | 好(易于人眼阅读和调试) | 好(易于人眼阅读和调试) |
数据类型校验 | 强(通过 .proto 文件明确定义,编译时检查) | 弱(无模式,需要额外校验) | 弱(可通过 XSD 但非强制) |
版本兼容性 | 非常好(向后/向前兼容的明确规则) | 依赖实现(需要手动处理缺失字段) | 依赖实现(需要手动处理) |
跨语言支持 | 官方支持多种语言,生成代码API一致 | 所有语言都支持,但行为可能略有差异 | 所有语言都支持 |
学习成本 | 中(需要定义 .proto 文件并编译) | 低(结构简单,立即可用) | 中(结构相对复杂) |
总结一下 Protobuf 的优势:
性能极致:二进制编码带来的小体积和高序列化速度,直接转化为更低的网络带宽消耗和更快的响应时间。
强契约性:
.proto
文件作为唯一的数据契约,明确了数据结构,保证了前后端、多服务之间数据格式的一致性,避免了“猜测”字段含义的麻烦。强大的版本化:通过
required/optional
和字段编号的规则,可以轻松地演进 API 而不会破坏旧客户端,这是其设计上的一大亮点。代码生成:一键生成多语言代码,减少了手动编写解析代码的工作量和出错概率。
四、Protobuf 的典型适用场景
基于以上优势,Protobuf 在以下场景中几乎是无可争议的最佳选择:
1. 微服务间的通信(特别是 gRPC)
这是 Protobuf 最经典、最主流的应用场景。gRPC 默认就使用 Protobuf 作为其接口定义语言(IDL)和底层的消息交换格式。
为什么? 微服务架构中,服务间调用非常频繁,对性能和延迟极其敏感。Protobuf 的高性能和小体积能显著提升整个系统的吞吐量。
例子:你的
User.proto
文件完美地定义了客户端和服务端之间的通信消息格式。
2. 高性能网络编程
任何对性能有苛刻要求的网络通信场景,如:
游戏后台:游戏客户端与服务器之间同步大量的玩家状态、位置、动作信息。
金融交易系统:股票行情推送、高频交易,每毫秒都至关重要。
即时通讯(IM):需要快速传递海量的聊天消息、状态通知。
3. 数据存储和缓存
需要将结构化数据序列化后存储到磁盘或缓存中。
为什么? 相比 JSON,Protobuf 占用空间更小,读写更快。
例子:将用户会话信息、复杂的配置对象用 Protobuf 序列化后存入 Redis 或直接保存为文件。
4. 设备间通信(IoT)
在物联网领域,设备资源(电量、算力、网络带宽)通常非常有限。
为什么? Protobuf 极小的数据体积可以节省宝贵的网络带宽和电量。
五、protobuf的使用:三大核心步骤
1.定义数据结构
在一个 .proto
文件中,定义数据格式。
// 1. 定义
syntax = "proto3";
message Person {int32 id = 1;string name = 2;string email = 3;
}
2、生成代码
使用 protoc
编译器(需提交安装好,安装步骤在另一篇博客中介绍),根据 .proto
文件自动生成目标语言(如 Java, C++, Go, Python)的类代码。
# 2. 生成代码
protoc --java_out=. person.proto
3.在程序中使用
在应用程序中,使用生成的类来序列化和反序列化数据。
// 3. 使用 (Java示例)
// 构建对象
Person person = Person.newBuilder().setId(1234).setName("张三").setEmail("zhangsan@example.com").build();// 序列化为字节数组 (用于网络发送或存储)
byte[] data = person.toByteArray();// 从字节数组反序列化回对象
Person receivedPerson = Person.parseFrom(data);