如何查看网站的点击量,电影资源网站怎么做的,专业的手机网站建设公司排名,dedecms部署两个网站Protobuf 3 入门
1. 什么是序列化#xff1f;
1.1 概念
序列化#xff08;Serialization 或 Marshalling#xff09; 是指将数据结构或对象的状态转换成可存储或传输的格式。反向操作称为反序列化#xff08;Deserialization 或 Unmarshalling#xff09;#xff0c;它…Protobuf 3 入门
1. 什么是序列化
1.1 概念
序列化Serialization 或 Marshalling 是指将数据结构或对象的状态转换成可存储或传输的格式。反向操作称为反序列化Deserialization 或 Unmarshalling它的作用是将序列化的数据恢复成原始的数据结构或对象。
简单来说序列化就像“打包”反序列化就像“解包”。
2.1 为什么需要序列化
在计算机系统中数据通常是以内存中的对象如 struct、class形式存在的而内存数据不能直接在不同程序之间传输必须先转换成可存储或可传输的格式。序列化的主要用途包括
数据存储将数据保存到文件、数据库等例如 日志文件配置文件如 JSON、YAML持久化存储如 Redis、MongoDB 数据传输在不同进程或网络之间传输数据例如 前端和后端通信Web API微服务之间的通信远程调用RPC如 gRPC 数据缓存比如将复杂的对象序列化后存入 Redis提高访问速度。跨语言兼容不同编程语言的数据结构不一样序列化后可以在不同语言之间传输数据。
3.1 序列化的方式
不同的序列化格式适用于不同的应用场景常见的格式包括
格式特点可读性序列化速度数据大小适用场景JSON文本格式广泛使用可读适中较大Web API前端后端通信XML结构化文本标签冗余可读慢大早期 Web API配置文件YAML结构更简洁适合人阅读可读适中较大配置文件Kubernetes、DockerProtobufGoogle 开发的高效二进制格式不可读快小微服务、gRPC、高性能应用MessagePack类似 JSON但体积更小不可读快小移动端、嵌入式系统ThriftFacebook 开发的高效序列化格式不可读快小分布式系统RPCAvro适用于大数据如 Hadoop不可读适中小大数据处理BSONMongoDB 的序列化格式不可读适中适中MongoDB 数据存储
2. 什么是 Protobuf
2.1 概念
ProtobufProtocol Buffers是 Google 开发的一种高效、跨平台、可扩展的数据序列化协议。它可以将数据转换为紧凑的二进制格式用于不同系统之间进行高效的数据传输和存储。
简单理解
它类似于 JSON但比 JSON 体积更小、速度更快。它类似于 XML但格式更紧凑、解析更高效。它适用于微服务、RPC远程调用、数据存储等高性能场景。
2.2 为什么使用 Protobuf
特点ProtobufJSONXML格式二进制文本文本体积最小较大最大解析速度最快一般最慢可读性不可读可读可读跨语言支持是是是支持 RPC是gRPC否否
如果你的项目涉及
高性能数据通信微服务、RPC、物联网、游戏服务器跨语言数据传输Go、Java、Python、C、Rust 等大规模数据存储日志、数据库、缓存
那么 Protobuf 是比 JSON、XML 更好的选择。
2.3 Protobuf 的使用场景
微服务通信gRPC 适用于 Go、Java、Python、C 等语言的微服务之间高效通信。结合 gRPC 使用可以比传统 REST API 更快。 数据存储 存储日志、缓存数据如存入 Redis时Protobuf 体积小能节省存储空间。 跨语言数据交换 由于 Protobuf 支持多种编程语言可以在不同语言的系统之间进行高效数据传输。 移动端和 IoT物联网 移动端和 IoT 设备通常带宽和存储受限Protobuf 适用于传输小体积数据提高性能。
3. 简单解释 Protobuf 例子
3.1 Protobuf 文件 simple.proto
syntax proto3; // 使用 proto3 语法message SearchRequest { // 定义一个数据结构类似 JSON 对象string query 1; // 搜索关键词字符串int32 page_number 2; // 页码整数int32 result_per_page 3; // 每页返回的结果数整数
}解释
syntax proto3; 指定使用 proto3 语法。message SearchRequest 定义了一个数据结构类似 JSON 对象。每个字段的格式 类型string、int32字段名称query、page_number、result_per_page字段编号1、2、3用于唯一标识字段不能重复
3.2 编译 Protobuf 代码
Protobuf 需要编译后才能用于编程语言Go、Java、Python 等。 在终端运行
protoc --go_out. simple.protoprotoc 是 Protobuf 编译器--go_out. 表示生成 Go 代码并存放在当前目录simple.proto 是需要编译的 Protobuf 文件
不同语言对应的参数
语言编译参数C--cpp_out.Java--java_out.Python--python_out.C#--csharp_out.Rust--rust_out.
最终会生成 simple.pb.go这个文件包含 Go 代码用于操作 SearchRequest 结构。
3.3 生成的 Go 代码
编译后会生成如下 Go 结构
type SearchRequest struct {Query string protobuf:bytes,1,opt,namequery,proto3 json:query,omitemptyPageNumber int32 protobuf:varint,2,opt,namepage_number,jsonpageNumber,proto3 json:page_number,omitemptyResultPerPage int32 protobuf:varint,3,opt,nameresult_per_page,jsonresultPerPage,proto3 json:result_per_page,omitempty
}解释
SearchRequest 是 struct对应 .proto 文件中的 message SearchRequest。Query、PageNumber、ResultPerPage 变量对应 .proto 里的字段。protobuf:... 里的信息用于 Protobuf 序列化和解析。
3.4 如何使用这个 Go 结构
package mainimport (fmtgoogle.golang.org/protobuf/proto
)func main() {// 创建 SearchRequest 实例request : SearchRequest{Query: golang protobuf,PageNumber: 1,ResultPerPage: 10,}// **序列化**data, _ : proto.Marshal(request)// **反序列化**newRequest : SearchRequest{}proto.Unmarshal(data, newRequest)fmt.Println(newRequest) // 输出: {Query:golang protobuf PageNumber:1 ResultPerPage:10}
}解释
创建 SearchRequest 结构并填充数据。使用 proto.Marshal(request) 序列化转换成二进制格式适合网络传输。使用 proto.Unmarshal(data, newRequest) 反序列化把二进制恢复成 Go 结构。
4. Protobuf 的数据类型
4.1 标量数据类型Scalar Types
Protobuf 提供了一些常见的基本数据类型对应不同语言的变量类型。
4.1.1 数值类型
Protobuf 类型说明适用场景int3232 位整数默认编码适用于较小的整数int6464 位整数默认编码适用于较大的整数uint32无符号 32 位整数适用于只能为正数的情况uint64无符号 64 位整数适用于大数且不允许负数sint3232 位有符号整数ZigZag 编码适用于可能包含负数的整数sint6464 位有符号整数ZigZag 编码适用于包含负数的长整数fixed3232 位整数固定长度编码适用于数值分布较均匀的场景fixed6464 位整数固定长度编码适用于较大的定长整数sfixed3232 位有符号整数固定长度编码适用于负数较多的场景sfixed6464 位有符号整数固定长度编码适用于较大的负数 区别 int32/int64默认使用 Varint 编码数据小的时候占用字节更少。sint32/sint64使用 ZigZag 编码负数编码更高效。fixed32/fixed64使用固定长度存储适合数值分布均匀的情况。sfixed32/sfixed64固定长度的有符号整数。 4.1.2 浮点数类型
Protobuf 类型说明适用场景float32 位浮点数适用于存储小数double64 位浮点数适用于更高精度的小数 注意 float 占 4 个字节精度有限。double 占 8 个字节适用于更高精度计算。 4.1.3 布尔类型
Protobuf 类型说明适用场景bool布尔值 (true/false)适用于开关、状态等
示例
message Example {bool is_active 1; // true or false
}4.1.4 字符串和字节类型
Protobuf 类型说明适用场景stringUTF-8 编码的字符串存储文本信息bytes原始字节数据适用于存储二进制数据如文件、图片等
示例
message Example {string name 1;bytes file_data 2;
}注意 string 只能存储 文本UTF-8 编码。bytes 可以存储 任意二进制数据如图片、视频等。 4.2 复杂数据类型
4.2.1 数组Repeated
使用 repeated 关键字表示 列表/数组
message Example {repeated string hobbies 1;repeated int32 scores 2;
}repeated string hobbies 1; → 表示字符串数组repeated int32 scores 2; → 表示整数数组 注意 在 Protobuf 3 中repeated 类型默认是可选的不需要额外的 optional 关键字。 4.2.2 键值对Map
Protobuf 3 提供 mapK, V 类型来存储键值对
message Example {mapstring, int32 scores 1; // key: string, value: int32
}mapstring, int32 表示 键为字符串值为整数 的字典。生成代码后会转换成 Go 语言的 map[string]int32。
4.2.3 枚举类型Enum
enum Status {UNKNOWN 0; // 枚举必须从 0 开始ACTIVE 1;INACTIVE 2;
}message User {Status status 1;
}enum 只能用于定义固定的值类似 int。第一个枚举值必须是 0防止解析错误。
4.2.4 嵌套 Message
message Address {string city 1;string street 2;
}message Person {string name 1;Address address 2; // 直接嵌套 Address
}Person 结构里包含 Address 结构可以用于复杂数据存储。
4.3 Protobuf 类型与不同语言的对应关系
Protobuf 类型GoJavaPythonCint32int32intintint32_tint64int64longintint64_tfloatfloat32floatfloatfloatdoublefloat64doublefloatdoubleboolboolbooleanboolboolstringstringStringstrstd::stringbytes[]bytebyte[]bytesstd::stringmapK,Vmap[K]VMapK,Vdictstd::mapK,Vrepeated[]TListTliststd::vectorT 4.4 Protobuf 3 语法示例
syntax proto3;message Person {string name 1;int32 age 2;bool is_active 3;repeated string hobbies 4;mapstring, int32 scores 5;
}这个 Person 结构包含
string name → 姓名int32 age → 年龄bool is_active → 是否激活repeated string hobbies → 兴趣爱好数组mapstring, int32 scores → 课程成绩键值对
5. Protobuf 其他字段
5.1 Oneof互斥字段
5.1.1 什么是 oneof
oneof 关键字用于定义一组互斥字段即同一时间只能有一个字段被设置。它的作用类似于 C 语言的 union但比 union 更智能可以判断当前设置的是哪个字段。
5.1.2 为什么要用 oneof
在 proto3 版本中所有字段都有默认值比如
message Example {int64 id 1;
}如果 id 没有被设置默认值是 0。但如果 id 被显式设置为 0你就无法判断这个 0 是默认值还是用户真的设置了 0。
oneof 解决了这个问题因为它提供了一个字段状态检查功能让你可以判断哪个字段被设置了。
5.1.3 oneof 语法
message Response {oneof result {string success_message 1; // 成功时的消息int32 error_code 2; // 失败时的错误码}
}oneof 内的字段是互斥的最多只能设置一个。如果 success_message 被设置error_code 就不能被设置反之亦然。如果不设置任何字段oneof 字段为空。 适用场景 API 响应成功返回 success_message失败返回 error_code。状态表示例如订单可能是“待支付”或“已完成”但不能同时处于这两个状态。 5.1.4 oneof 在 Go 语言中的使用
Protobuf 生成的 Go 代码会使用 isXxx() 方法 来判断哪个字段被赋值。
示例Go 代码
package mainimport (fmtgoogle.golang.org/protobuf/proto
)// 假设 Protobuf 生成的 Go 结构如下
type Response struct {// 这是 oneof 生成的字段Result isResponse_Result protobuf_oneof:result
}type isResponse_Result interface {isResponse_Result()
}type Response_SuccessMessage struct {SuccessMessage string
}type Response_ErrorCode struct {ErrorCode int32
}// 实现 isResponse_Result 接口
func (*Response_SuccessMessage) isResponse_Result() {}
func (*Response_ErrorCode) isResponse_Result() {}func main() {// **成功时返回 success_message**resp1 : Response{Result: Response_SuccessMessage{SuccessMessage: Operation successful}}// **失败时返回 error_code**resp2 : Response{Result: Response_ErrorCode{ErrorCode: 404}}// 判断是哪个字段被设置switch v : resp1.Result.(type) {case *Response_SuccessMessage:fmt.Println(Success:, v.SuccessMessage)case *Response_ErrorCode:fmt.Println(Error:, v.ErrorCode)}switch v : resp2.Result.(type) {case *Response_SuccessMessage:fmt.Println(Success:, v.SuccessMessage)case *Response_ErrorCode:fmt.Println(Error:, v.ErrorCode)}
}输出
Success: Operation successful
Error: 404oneof 生成了 isResponse_Result 接口允许我们判断哪个字段被设置。switch v : resp.Result.(type) 语法用于检查当前 oneof 字段的类型。
5.1.5 oneof 的应用场景
REST API / gRPC 响应成功返回数据失败返回错误码订单状态未支付 / 已支付用户身份验证邮箱登录 / 手机号登录存储不同类型的数据文本 / 图片 / 视频
5.2 Reserved保留字段
5.2.1 什么是reserved
在 Protobuf 3 中reserved 关键字用于保留字段编号或名称防止将来代码演进时误用已删除的字段。 这可以避免 API 变更时的兼容性问题确保旧数据不会被错误解析。
5.2.2 为什么需要 reserved
当你删除或修改字段时如果不使用 reserved那么
未来新添加的字段可能意外复用旧的字段编号导致数据解析出错。旧数据仍然可能使用被删除的字段导致意外行为。
5.2.3 reserved 语法
你可以用 reserved 关键字保留字段编号或名称防止后续被重新使用。
保留字段编号
message User {reserved 2, 4 to 6; // 不能再使用编号 2、4、5、6
}以后不能再使用 2、4、5、6 作为字段编号。如果后续尝试用 field 2;编译时会报错。
保留字段名称
message User {reserved old_name, deprecated_field; // 不能再使用这些字段名
}以后不能再使用 “old_name” 和 “deprecated_field” 作为字段名。
同时保留编号和名称
message User {reserved 2, 4 to 6;reserved old_name, deprecated_field;
}这样可以同时保留编号和字段名称防止意外复用。
5.2.4 reserved 作用
避免旧数据解析错误如果编号被误用旧数据可能被错误解析。防止 API 兼容性问题如果 API 变更保留字段可以确保旧客户端不会收到无效数据。让代码更可维护明确告诉后续开发者哪些字段不能使用。
5.3 Any存储任意数据
5.3.1 什么是 Any
Any 是 Protobuf 3 提供的一种特殊类型允许存储任意类型的 Protobuf 消息适用于动态数据场景。 它可以在不修改 .proto 结构的情况下支持不同类型的数据类似于 JSON 里的 object 或 mapstring, any。
5.3.2 Any 的作用
存储动态数据如果一个字段的类型可能变化例如可能是 User 或 Order可以使用 Any 而不需要改 .proto 文件。实现灵活的 API 设计适用于插件系统、事件系统、日志系统让不同的子系统传递不同的数据结构。避免频繁修改 Protobuf 定义当不同的客户端需要传输不同的数据类型时使用 Any 可以减少 API 变更的影响。
5.3.3 Any 的基本用法
1导入 Any 类型
Any 需要导入 google/protobuf/any.proto
import google/protobuf/any.proto; // 引入 Any 类型message Response {string message 1; google.protobuf.Any data 2; // 存储任意类型
}message 是普通字段存储文本信息。data 是 Any 类型可以存储任何 Protobuf 消息。
2嵌套不同的消息
假设你有两种不同的消息 User 和 Order
message User {string name 1;int32 age 2;
}message Order {int32 order_id 1;double price 2;
}message Response {string message 1;google.protobuf.Any data 2; // 可以存储 User 或 Order
}data 字段可以存储 User 或 Order而不需要修改 Response 结构。这样Response 可以在不同场景下使用不受数据类型影响。
5.3.4 Any 在 Go 语言中的使用
1安装 Protobuf 依赖
在 Go 代码中需要 proto 和 anypb处理 Any 类型
go get google.golang.org/protobuf/proto
go get google.golang.org/protobuf/types/known/anypb2Go 代码示例
package mainimport (fmtgoogle.golang.org/protobuf/protogoogle.golang.org/protobuf/types/known/anypb
)// 定义 User 和 Order 结构
type User struct {Name stringAge int32
}type Order struct {OrderId int32Price float64
}// Response 结构包含 Any 字段
type Response struct {Message stringData *anypb.Any
}func main() {// 创建 User 结构user : User{Name: Alice, Age: 25}// 将 User 结构封装到 Any 里anyData, _ : anypb.New(user)// 创建 Response 并存储 User 数据resp : Response{Message: User data,Data: anyData,}// **序列化**data, _ : proto.Marshal(resp)// **反序列化**newResp : Response{}proto.Unmarshal(data, newResp)// **解析 Any 字段**newUser : User{}newResp.Data.UnmarshalTo(newUser)fmt.Println(Message:, newResp.Message)fmt.Println(User Name:, newUser.Name, Age:, newUser.Age)
}3Go 代码解释
封装数据 使用 anypb.New(user) 把 User 结构转换成 Any 类型。 序列化 Response 使用 proto.Marshal(resp) 进行序列化便于存储或传输。 反序列化 Response 使用 proto.Unmarshal(data, newResp) 解析 Response 结构。 解析 Any 数据 newResp.Data.UnmarshalTo(newUser) 解析 Any 字段恢复 User 结构。
6. Protobuf 编码原理
Protobuf 使用高效的二进制格式来存储和传输数据其中最关键的编码方式之一是 Varint变长整数编码。它的核心思想是
数值越小占用字节越少数值越大占用字节越多高效存储减少带宽消耗
6.1 什么是 Varint
Varint变长整数编码 是一种特殊的编码方式它可以使用 1 到 N 个字节 表示整数。
小数占用更少字节如 1 只需要 1 个字节。大数会自动扩展到多个字节如 300 需要 2 个字节。
6.2 Varint 编码规则
每个字节的最高位MSBMost Significant Bit是“是否还有后续字节的标志” 最高位为 0表示这是最后一个字节。最高位为 1表示后面还有字节。 剩下的 7 位存储数据低位优先LSB。
6.3 具体示例
1数字 1 的 Varint 编码
0000 0001 只有 1 个字节最高位 0表示这是最后一个字节。其余 7 位 000 0001 1。
存储方式
[0000 0001] → 1 字节2数字 300 的 Varint 编码
先看二进制表示
300 1001011009 位需要拆成 7 位 剩余部分
低 7 位 0101100 → 0x2C44
高 2 位 0000010 → 0x022第一字节1010 11000xAC 最高位 1表示后面还有字节。剩余 7 位存 010 1100 44。 第二字节0000 00100x02 最高位 0表示这是最后一个字节。剩余 7 位存 000 0010 2。
最终编码
[1010 1100] [0000 0010] → 2 字节0xAC 0x026.4 Wire Type数据类型编码
Protobuf 数据存储为 键值对key-value 形式每个字段的 key 也需要编码。 字段的 key 由字段编号 Wire Type 组成。
Wire Type值作用Varint0变长整数int32, int64, bool, enumFixed64164 位定长double, fixed64, sfixed64Length-delimited2变长数据string, bytes, message, repeatedStart group3已废弃用于嵌套数据End group4已废弃Fixed32532 位定长float, fixed32, sfixed32
存储格式
[字段编号 3] | [Wire Type] [数据]字段编号左移 3 位低 3 位存 Wire Type。
6.5 例子Protobuf 编码解析
假设 Person 结构如下
message Person {int32 id 1; // 1 字段编号string name 2; // 2 字段编号
}数据
{id: 150,name: Alice
}编码过程 字段 id 150 字段编号 1Wire Type 0Varintkey (1 3) | 0 0000 1000 (0x08)150 的 Varint 编码1001 0110 0000 00010x96 0x01 最终存储 [0x08] [0x96 0x01] 字段编号 1Varint字段 name Alice 字段编号 2Wire Type 2Length-delimited字符串key (2 3) | 2 0001 0001 (0x12)Alice 5 个字节0x41 0x6C 0x69 0x63 0x65 最终存储 [0x12] [0x05] [0x41 0x6C 0x69 0x63 0x65]6.6 解析 Protobuf 二进制数据
假设收到如下二进制数据
08 96 01 12 05 41 6C 69 63 65逐字节解析
08 0000 1000字段编号 1Wire Type 0Varint96 01 150Varint 解码12 0001 0010字段编号 2Wire Type 2字符串05 长度 541 6C 69 63 65 Alice
最终解析为
{id: 150,name: Alice
}