go语言测试Protobuf编码解码过程(附测试代码)

 

简介

Google Protocol Buffer( 简称 Protobuf)是Google公司内部的混合语言数据标准,他们主要用于RPC系统和持续数据存储系统。

Protobuf应用场景

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

简单来说,Protobuf的功能类似于XML,即负责把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议等使用场景。

为什么已经有了XLM,JSON等已经很普遍的数据传输方式,还要设计出Protobuf这样一种新的数据协议呢?

 

Protobuf 优点

    • 性能好/效率高

      • 时间维度:采用XML格式对数据进行序列化时,时间消耗上性能尚可;对于使用XML格式对数据进行反序列化时的时间花费上,耗时长,性能差。
      • 空间维度:XML格式为了保持较好的可读性,引入了一些冗余的文本信息。所以在使用XML格式进行存储数据时,也会消耗空间。

      整体而言,Protobuf以高效的二进制方式存储,比XML小3到10倍,快20到100倍。

    • 代码生成机制

      • 代码生成机制的含义

        在Go语言中,可以通过定义结构体封装描述一个对象,并构造一个新的结构体对象。比如定义Person结构体,并存放于Person.go文件:

        1 type Person struct{
        2     Name string
        3     Age int
        4     Sex int
        5 }
        在分布式系统中,因为程序代码时分开部署,比如分别为A、B。A系统在调用B系统时,无法直接采用代码的方式进行调用,因为A系统中不存在B系统中的代码。因此,A系统只负责将调用和通信的数据以二进制数据包的形式传递给B系统,由B系统根据获取到的数据包,自己构建出对应的数据对象,生成数据对象定义代码文件。这种利用编译器,根据数据文件自动生成结构体定义和相关方法的文件的机制被称作代码生成机制。
      • 代码生成机制的优点 首先,代码生成机制能够极大解放开发者编写数据协议解析过程的时间,提高工作效率;其次,易于开发者维护和迭代,当需求发生变更时,开发者只需要修改对应的数据传输文件内容即可完成所有的修改。

    • 支持“向后兼容”和“向前兼容”

      • 向后兼容:在软件开发迭代和升级过程中,"后"可以理解为新版本,越新的版本越靠后;而“前”意味着早起的版本或者先前的版本。向“后”兼容即是说当系统升级迭代以后,仍然可以处理老版本的数据业务逻辑。
      • 向前兼容:向前兼容即是系统代码未升级,但是接受到了新的数据,此时老版本生成的系统代码可以处理接收到的新类型的数据。

      支持前后兼容是非常重要的一个特点,在庞大的系统开发中,往往不可能统一完成所有模块的升级,为了保证系统功能正常不受影响,应最大限度保证通讯协议的向前兼容和向后兼容。

    • 支持多种编程语言 Protobuf不仅仅Google开源的一个数据协议,还有很多种语言的开源项目实现。

Protobuf 缺点

  • 可读性较差

    为了提高性能,Protobuf采用了二进制格式进行编码。二进制格式编码对于开发者来说,是没办法阅读的。在进行程序调试时,比较困难。

  • 缺乏自描述 诸如XML语言是一种自描述的标记语言,即字段标记的同时就表达了内容对应的含义。而Protobuf协议不是自描述的,Protobuf是通过二进制格式进行数据传输,开发者面对二进制格式的Protobuf,没有办法知道所对应的真实的数据结构,因此在使用Protobuf协议传输时,必须配备对应的proto配置文件。

 

下面通过实际操作,感受下protobuf编码解码过程。

test.proto

1 syntax = "proto3";
2 package example;
3 option go_package="../testpb";
4 
5 message Person {
6     string Name = 1;
7     int32 Age = 2;
8     string From = 3;
9 }

生成go代码 test.pb.go

protoc --go_out=. .\test.proto

 test.pb.go

  1 // Code generated by protoc-gen-go. DO NOT EDIT.
  2 // versions:
  3 //     protoc-gen-go v1.26.0
  4 //     protoc        v3.18.0
  5 // source: test.proto
  6 
  7 package testpb
  8 
  9 import (
 10     protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 11     protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 12     reflect "reflect"
 13     sync "sync"
 14 )
 15 
 16 const (
 17     // Verify that this generated code is sufficiently up-to-date.
 18     _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
 19     // Verify that runtime/protoimpl is sufficiently up-to-date.
 20     _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 21 )
 22 
 23 type Person struct {
 24     state         protoimpl.MessageState
 25     sizeCache     protoimpl.SizeCache
 26     unknownFields protoimpl.UnknownFields
 27 
 28     Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
 29     Age  int32  `protobuf:"varint,2,opt,name=Age,proto3" json:"Age,omitempty"`
 30     From string `protobuf:"bytes,3,opt,name=From,proto3" json:"From,omitempty"`
 31 }
 32 
 33 func (x *Person) Reset() {
 34     *x = Person{}
 35     if protoimpl.UnsafeEnabled {
 36         mi := &file_test_proto_msgTypes[0]
 37         ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 38         ms.StoreMessageInfo(mi)
 39     }
 40 }
 41 
 42 func (x *Person) String() string {
 43     return protoimpl.X.MessageStringOf(x)
 44 }
 45 
 46 func (*Person) ProtoMessage() {}
 47 
 48 func (x *Person) ProtoReflect() protoreflect.Message {
 49     mi := &file_test_proto_msgTypes[0]
 50     if protoimpl.UnsafeEnabled && x != nil {
 51         ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 52         if ms.LoadMessageInfo() == nil {
 53             ms.StoreMessageInfo(mi)
 54         }
 55         return ms
 56     }
 57     return mi.MessageOf(x)
 58 }
 59 
 60 // Deprecated: Use Person.ProtoReflect.Descriptor instead.
 61 func (*Person) Descriptor() ([]byte, []int) {
 62     return file_test_proto_rawDescGZIP(), []int{0}
 63 }
 64 
 65 func (x *Person) GetName() string {
 66     if x != nil {
 67         return x.Name
 68     }
 69     return ""
 70 }
 71 
 72 func (x *Person) GetAge() int32 {
 73     if x != nil {
 74         return x.Age
 75     }
 76     return 0
 77 }
 78 
 79 func (x *Person) GetFrom() string {
 80     if x != nil {
 81         return x.From
 82     }
 83     return ""
 84 }
 85 
 86 var File_test_proto protoreflect.FileDescriptor
 87 
 88 var file_test_proto_rawDesc = []byte{
 89     0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x65, 0x78,
 90     0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0x42, 0x0a, 0x06, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12,
 91     0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e,
 92     0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x41, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
 93     0x52, 0x03, 0x41, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20,
 94     0x01, 0x28, 0x09, 0x52, 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2e, 0x2f,
 95     0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 96 }
 97 
 98 var (
 99     file_test_proto_rawDescOnce sync.Once
100     file_test_proto_rawDescData = file_test_proto_rawDesc
101 )
102 
103 func file_test_proto_rawDescGZIP() []byte {
104     file_test_proto_rawDescOnce.Do(func() {
105         file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
106     })
107     return file_test_proto_rawDescData
108 }
109 
110 var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
111 var file_test_proto_goTypes = []interface{}{
112     (*Person)(nil), // 0: example.Person
113 }
114 var file_test_proto_depIdxs = []int32{
115     0, // [0:0] is the sub-list for method output_type
116     0, // [0:0] is the sub-list for method input_type
117     0, // [0:0] is the sub-list for extension type_name
118     0, // [0:0] is the sub-list for extension extendee
119     0, // [0:0] is the sub-list for field type_name
120 }
121 
122 func init() { file_test_proto_init() }
123 func file_test_proto_init() {
124     if File_test_proto != nil {
125         return
126     }
127     if !protoimpl.UnsafeEnabled {
128         file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
129             switch v := v.(*Person); i {
130             case 0:
131                 return &v.state
132             case 1:
133                 return &v.sizeCache
134             case 2:
135                 return &v.unknownFields
136             default:
137                 return nil
138             }
139         }
140     }
141     type x struct{}
142     out := protoimpl.TypeBuilder{
143         File: protoimpl.DescBuilder{
144             GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
145             RawDescriptor: file_test_proto_rawDesc,
146             NumEnums:      0,
147             NumMessages:   1,
148             NumExtensions: 0,
149             NumServices:   0,
150         },
151         GoTypes:           file_test_proto_goTypes,
152         DependencyIndexes: file_test_proto_depIdxs,
153         MessageInfos:      file_test_proto_msgTypes,
154     }.Build()
155     File_test_proto = out.File
156     file_test_proto_rawDesc = nil
157     file_test_proto_goTypes = nil
158     file_test_proto_depIdxs = nil
159 }

 

main.go

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "os"
 6     "yuzyong/testpb"
 7 
 8     "github.com/golang/protobuf/proto"
 9 )
10 
11 func main() {
12     fmt.Println("Hello World. \n")
13 
14     msg_test := &testpb.Person{
15         Name: *proto.String("Davie"),
16         Age:  *proto.Int(18),
17         From: *proto.String("China"),
18     }
19 
20     //序列化
21     msgDataEncoding, err := proto.Marshal(msg_test)
22     if err != nil {
23         panic(err.Error())
24         return
25     }
26 
27     // fmt.Printf("%T\n", msgDataEncoding)
28     fmt.Printf("二进制打印:[%v]\n", msgDataEncoding)
29     fmt.Printf("长度:[%v]\n", len(msgDataEncoding))
30     fmt.Printf("字符串打印:[%s]\n", msgDataEncoding)
31 
32     fmt.Println("\n")
33 
34     //反序列化
35     msgEntity := testpb.Person{}
36     err = proto.Unmarshal(msgDataEncoding, &msgEntity)
37     if err != nil {
38         fmt.Println(err.Error())
39         os.Exit(1)
40         return
41     }
42 
43     fmt.Printf("姓名:%s\n\n", msgEntity.GetName())
44     fmt.Printf("年龄:%d\n\n", msgEntity.GetAge())
45     fmt.Printf("国籍:%s\n\n", msgEntity.GetFrom())
46 }

 

 

运行结果:

 

 

 序列化后长度为16字节,二进制如下所示:

 

总结

 

①数字使用varint来表示,所以上图中的数字18 只用一个字节表示(数字越大,其长度会动态调整而变长)。

②字符串在序列化后完整保留,其前面跟一个字符串长度。

③序列化之后的格式也是key-value组合而成,即上图中粉色方框中的字段为key值。key值的定义如下:

(field_number << 3) | wire_type

  可以看到 Key 由两部分组成。第一部分是 field_number,比如消息Person.Name中field id 的field_number为1。第二部分为wire_type。表示 Value的传输类型。而wire_type有以下几种类型:

 

 

组合起来Name的key值为1010,转换为十进制即为上上图中序列化后的第一个字节10

 

posted on 2021-10-08 17:17  yuzyong  阅读(530)  评论(0编辑  收藏  举报