02.protobuf
# 01.protobuf
# 1、What 是什么
- Protocol Buffers(Protobuf) 是由 Google 开发的一种语言中立、平台无关、可扩展的序列化协议,用于高效地结构化数据交换
- 在 Golang 中,Protobuf 常用于与 gRPC 结合,通过定义数据结构来实现跨语言的数据序列化与反序列化
- Protobuf 文件通常以
.proto
为后缀,通过编译器生成相应的 Golang 代码,简化了数据传输的过程
# 2、Why 为什么使用
- 高效传输:
- Protobuf 使用二进制格式,数据传输效率远高于基于文本格式的 JSON 或 XML
- 它序列化后的数据占用空间小,反序列化速度快,非常适合性能要求高的系统
- 跨语言支持:
- Protobuf 支持多种编程语言(如 Golang、Java、Python 等)
- 允许不同语言的系统通过统一的格式进行通信
- 结构化数据:
- Protobuf 能够明确定义消息的数据结构
- 通过
.proto
文件维护字段类型和顺序,从而确保数据一致性
- 向后兼容性:
- Protobuf 的字段编号机制允许向后兼容,支持在不破坏旧系统的情况下添加新字段
# 3、When 什么时候使用
- 大规模分布式系统:当系统需要在不同微服务之间传输大量数据时,Protobuf 提供了高效的序列化和反序列化方式
- 需要跨语言通信:如果不同的服务由不同编程语言开发,Protobuf 可以确保数据格式一致,减少数据解析的复杂性
- 高性能要求:在实时性要求高的场景中(如流媒体、实时数据处理),Protobuf 的轻量级数据结构和高效传输速度尤为重要
- gRPC 场景:Protobuf 是 gRPC 默认使用的序列化协议,适用于各种远程过程调用(RPC)场景
# 4、原理解析
# 1)字段编号
- 每个字段都有唯一的编号,数据在序列化时按照编号进行排列
- 编号越小,序列化后的字节越短,这使得 Protobuf 在网络传输中更高效
- 示例:假设你定义了一个包含用户信息的 Protobuf 消息
id = 1
对应的编号1
只需占用 1 个字节,而如果编号为128
,则需要使用 2 个字节- 即使消息内容相同,
SmallNumbers
的序列化结果会比LargeNumbers
更短,这在大量数据传输时能显著节省带宽和提升性能
message SmallNumbers {
string name = 1;
int32 age = 2;
}
message LargeNumbers {
string name = 128;
int32 age = 129;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2)二进制序列化
- Protobuf 将消息序列化为
紧凑的二进制格式
,减少了数据传输所需的字节数 - 这与 JSON 或 XML 的文本格式不同,极大减少了带宽消耗和 CPU 处理时间
- 示例:比较 Protobuf 和 JSON 格式在序列化后的结果
// 假设有以下 Protobuf 消息
message User {
int32 id = 1;
string name = 2;
bool is_active = 3;
}
// JSON 序列化需要传输的字节数为(总共约 46 字节)
{"id": 123, "name": "Alice", "is_active": true}
// Protobuf 二进制序列化,总共约 11 字节
08 7B 12 05 41 6C 69 63 65 18 01
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 二进制序列化解析为json推演
# 08 7B => "id": 123
`08` 字段编号为 1,数据类型为 Varint(整数)
`7B` 的十进制是 123
`12` 字段编号为 2,数据类型为 Length-delimited(字符串、字节数组)
`05` 表示接下来字符串的长度为 5 个字节
`41 6C 69 63 65` 这是字符串 "Alice" 的 ASCII 编码
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3)消息灵活性
- protobuf中只会新增不会删除字段,加入新增了
email
字段 - 旧版服务会忽略
email
字段的额外数据,但仍然能够正确解析id
和name
字段
message User {
int32 id = 1;
string name = 2;
string email = 3; // 新增字段
}
1
2
3
4
5
2
3
4
5
# 5、紧凑的二进制格式
- 紧凑的二进制格式指的是一种高效的数据编码方式,它使用尽量少的位数来表示数据,以减少数据存储和传输的字节数
- 在这种格式中,每一位或字节都经过压缩编码,最大化利用有限的存储空间
1)字段编号代替字段名
2)可变长度编码(Varint)
- 小的数字(例如 127)只使用 1 个字节。
- 大的数字(例如 128 或更大)则使用多个字节来表示。
3)类型和长度信息整合
- 字段编号通过一个“
字段编号 * 8 + 类型
”的规则进行编码。 - 例如,编号
1
且类型为 Varint 的字段会被编码为08
,其中1 * 8 + 0 = 8
,表示id
是一个整数类型。
- 字段编号通过一个“
4)长度前缀的使用
- 字符串
"Alice"
会被编码为05 41 6C 69 63 65
,其中05
表示后续 5 个字节是字符串内容
- 字符串
# 02.protobuf安装
# 1、protobuf说明
为了实现跨语言调用,在golang中实现RPC方法的时候我们应该选择一种跨语言的数据编解码方式,比如JSON
上述的
jsonrpc
可以满足此要求,但是也存在一些缺点,比如不支持http传输,数据编解码性能不高等于是呢,一些第三方rpc库都选择采用
protobuf
进行数据编解码,并提供一些服务注册代码自动生成功能Protobuf中最基本的
数据单元是message
,是类似Go语言中结构体
的存在- 在message中可以嵌套message或其它的基础数据类型的成员
# 2、安装protobuf
root@dev:opt# cd /opt
root@dev:opt# apt-get install libffi6=3.2.1-4 -y
root@dev:opt# sudo apt-get install autoconf automake libtool curl make g++ unzip libffi-dev -y
root@dev:opt# git clone https://github.com/protocolbuffers/protobuf.git
root@dev:protobuf# ./autogen.sh
root@dev:protobuf# ./configure
root@dev:protobuf# make && make install
root@dev:protobuf# sudo ldconfig
root@dev:protobuf# protoc -h
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 安装protobuf的go语言插件
# 1) 下载
root@dev:opt# cd /opt
root@dev:opt# export GOPROXY=https://goproxy.cn
root@dev:opt# go get -v -u github.com/golang/protobuf/proto
# 2) 进入到文件夹内进行编译
root@dev:opt# find / -name protoc-gen-go
root@dev:opt# cd /home/GOPATH/pkg/mod/github.com/golang/protobuf@v1.5.2/protoc-gen-go
root@dev:opt# go build
# 3)将生成的protoc-gen-go可执行文件,放在/bin目录下
root@dev:protoc-gen-go# cp protoc-gen-go /bin/
# 4)尝试补齐 protoc-gen-go
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 03.protobuf生成go文件格式
# 1、demo/pb/myproto.proto
// 采用proto3的语法(默认是 proto2)
syntax = "proto3";
option go_package = "./student";
// 指定所在包包名
package pb;
// 定义枚举类型
enum Week {
Monday = 0; // 枚举值,必须从 0 开始.
Turesday = 1;
}
// 定义消息体
message Student {
int32 age = 1; // 可以不从1开始, 但是不能重复. -- 不能使用 19000 - 19999
string name = 2;
People p = 3;
repeated int32 score = 4; // 数组
// 枚举
Week w = 5;
// 联合体
oneof data {
string teacher = 6;
string class = 7;
}
}
// 消息体可以嵌套
message People {
int32 weight = 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 2、myproto.pb.go
- 转成go语言格式
root@dev:db# protoc --go_out=./ *proto # 将所有.proto结尾的文件全部进行编译
root@dev:db# protoc --go_out=. myproto.proto # 指定对 myproto.proto进行编译
1
2
2
# 04.protobuf转json
# 1、db/class.proto
转成go语言格式
root@dev:db# protoc --go_out=. class.proto
1
// 默认是 proto2
syntax = "proto3";
option go_package = "./class";
// 指定所在包包名
package pb;
// 班级:定义消息体
message Class {
// 可以不从1开始, 但是不能重复. -- 不能使用 19000 - 19999
string class_name = 1;
Student stu = 2;
}
// 学生:消息体可以嵌套
message Student {
string name = 1;
int32 age = 2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2、db/class/class.pb.go
// 指定所在包包名
package class
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// 班级:定义消息体
type Class struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 可以不从1开始, 但是不能重复. -- 不能使用 19000 - 19999
ClassName string `protobuf:"bytes,1,opt,name=class_name,json=className,proto3" json:"class_name,omitempty"`
Stu *Student `protobuf:"bytes,2,opt,name=stu,proto3" json:"stu,omitempty"`
}
// 学生:消息体可以嵌套
type Student struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
}
func (x *Student) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Student) GetAge() int32 {
if x != nil {
return x.Age
}
return 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 3、test-proto.go
package main
import (
"demo/db/class"
"encoding/json"
"fmt"
)
func main() {
cls := &class.Class{
ClassName: "golang学神班级",
Stu: &class.Student{
Name: "zhangsan",
Age: 23,
},
}
var s, _ = json.Marshal(cls)
jsonStr := string(s)
fmt.Println(jsonStr)
// {"class_name":"golang学神班级","stu":{"name":"zhangsan","age":23}}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上次更新: 2024/10/15 16:27:13