|
gRPC中序列化结构化数据的默认方法.
什么是Protocol Buffers?
一种序列化数据的方法,该数据可以通过有线传输或存储在文件中。JSON和XML等其他格式也用于序列化数据。尽管这些平台已被证明具有极高的灵活性和有效性,但其中一个尚未完全优化的地方就是以平台无关的方式在多个微服务之间传输数据的场景 这就是使Google在2008年创建ProtoBuf格式的挑战。自那时以来,它已在Google内部广泛使用,并且一直是gRPC框架的默认数据格式。最初,Protobuf是为三种主要语言(C ++,Java和Python)创建的。多年来,Go,Ruby,JS,PHP,C#和Objective-C等许多语言也开始支持Protobuf。Protobuf的当前版本称为proto3。 像JSON和XML一样,Protobufs是语言和平台无关的。通过消除通常由数据格式完成的许多职责,并使Protobuf比JSON和XML更快,它仅专注于尽可能快地序列化和反序列化数据的能力。 另一个重要的优化方法是关于通过使传输的数据尽可能小来利用多少网络带宽。 要序列化的数据的定义写在称为原始文件(.proto)的配置文件中。这些文件将包含称为消息的配置。可以编译原始文件以使用用户的编程语言生成代码。 让我们详细了解Protobufs的主要功能。
主要特点
二进制传输格式
Protobuf是二进制传输格式,这意味着数据以二进制形式传输。与原始字符串相比,这提高了传输速度,因为它占用的空间和带宽更少。由于数据已压缩,因此CPU使用率也将降低。 唯一的缺点是Protobuf文件或数据的可读性不如JSON或XML 如果平台不像Rich Client那样支持二进制消息,则Protobuf中具有将二进制数据序列化为字符串的功能。
上下文和数据分离
在JSON和XML中,数据和上下文不是分开的-而在Protobuf中,它们是分开的。考虑一个JSON示例。
{
"first_name":"Arun",
"last_name":"Kurian"
}在此示例中,传输的数据具有一个对象,其具有两个属性first_name和last_name,值分别为Arun和Kurian。 这是高度可读的,但是会占用更多空间。在这里,每条JSON消息必须每次都提供这两部分。随着我们数据的增长,传输时间将大大增加。
但是对于Protobufs,情况就不同了。我们首先在这样的配置文件中定义一条消息:
{
string first_name = 1;
string last_name = 2;
}此配置文件包含上下文信息。数字只是字段的标识符。不必担心消息格式是否有些混乱-我们稍后将详细研究它。通过使用此配置,我们可以将数据编码为: 124Arun226Kurian 这里124Arun,
- 1表示字段标识,
- 2标识数据类型(String)
- 4是长度 当然必须承认这可读性比JSON差远了,但是与JSON数据相比,这将占用很少的空间。
消息格式
正如我们之前所看到的,数据是根据称为消息的配置以Protobuf的形式传输的。消息保存在.proto文件中。让我们看一个消息示例:
syntax = "proto3";
message Person {
uint64 id = 1;
string email = 2;
bool is_active = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}从上面的示例中,我们可以看到一条消息以message关键字声明,后跟用户定义的消息名称。文字或组件在大括号内声明。每个文字字段可以分为四个部分。他们是:
字段规则
在proto2 的Protobuf的版本,有规则required,optional和repeated,在名字段类型或数据类型之前。在proto3中对此进行了优化,仅repeated保留了规则。如果该字段表示相同类型的元素数组,则称该字段为重复字段。如果不重复该字段,则不应添加任何规则。
字段类型
字段可以容纳的数据类型分为三类。 第一个是标量数据类型,例如字符串和数字。第二种是enum数据类型。在我们的示例中,这是PhoneType。最终的数据类型是嵌入式消息(如PhoneNumber本例中的)。 像在JSON和XML中一样,当使用消息类型时,我们可以构建消息的层次结构来表示任何类型的数据。在的Protobuf提供的标量数据类型float,int32,int64,uint32,uint64,sint32,sint64,fixed32,fixed64,sfixed32,sfixed6,bool,string,和bytes。
字段名称
在Protobuf中命名字段时,要遵循一些约定,因为Protoc编译器会假定这些约定,因为它会根据.proto所选语言为文件生成代码。第一个约定是字段名称应全部小写。其次,如果字段名称中有多个单词,则应将它们用下划线分隔。
字段标签
字段标签是字段的数字表示形式,这使我们能够在定义中具有定义丰富的字段名称,而无需通过实际发送它们。 使用字段标签时,需要考虑某些事项。首先,字段标签在消息中必须唯一。其次,它们必须是整数。第三,如果要将字段从已经使用的定义中删除,则必须声明其标签reserved以防止重新定义它。例:reserved 8;
从原始文件生成代码
Protobuf最重要的组件是protoc编译器。protoc的安装如下:
Mac
brew tap homebrew/versions
brew install protobufUbuntu
sudo apt install protobuf-compiler安装之后需要测试
#测试
protoc --versions解释协议从.proto文件到不同语言生成代码的方式确实具有挑战性,因为各种生成代码的语法会有所不同。因此,这里只用JavaScript演示。我建议大家参考您喜欢的语言的官方文档。 首先,让我们创建一个小.proto文件。
syntax = "proto3";
message Person {
uint64 id = 1;
string email = 2;
}生成代码:
protoc --proto_path=src --js_out=js_out_folder src/person.proto结果类似
proto.Person = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.Person, jspb.Message);
proto.Person.prototype.getId = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
};
proto.Person.prototype.setId = function(value) {
return jspb.Message.setProto3IntField(this, 1, value);
};
proto.Person.prototype.getEmail = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
proto.Person.prototype.setEmail = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};我们可以看到Person创建了一个类,并为字段id和定义了getter和setter方法email。 根据语言,语法会有所不同。
结论
我们已经看到,协议缓冲区的设计比常规数据传输格式(如JSON或XML)更加高效和快速。数据压缩可以提高速度并减少CPU使用率。 我不能断言Protobuf是JSON的简单替代品,但它解决了JSON的许多问题,尤其是在使用微服务架构时。 一种折衷方案是大大降低了数据的可读性。与优点相比,这并不是一个很大的缺点。 |
|