乐趣区

关于protobuf:深入理解-ProtoBuf-原理与工程实践概述

ProtoBuf 作为一种跨平台、语言无关、可扩大的序列化构造数据的办法,已广泛应用于网络数据交换及存储。随着互联网的倒退,零碎的异构性会愈发突出,跨语言的需要会更加显著,同时 gRPC 也大有取代 Restful 之势,而 ProtoBuf 作为 g RPC 跨语言、高性能的法宝,咱们技术人有必要

深刻了解 ProtoBuf 原理,为当前的技术更新和选型打下基础。

我将过来的学习过程以及实践经验,总结成系列文章,与大家一起探讨学习,心愿大家能有所播种,当然其中有不正确的中央也欢送大家批评指正。

本系列文章次要蕴含:

  1. 深刻了解 ProtoBuf 原理与工程实际(概述)
  2. 深刻了解 ProtoBuf 原理与工程实际(编码)
  3. 深刻了解 ProtoBuf 原理与工程实际(序列化)
  4. 深刻了解 ProtoBuf 原理与工程实际(工程实际)

一、什么是 ProtoBuf

ProtoBuf(Protocol Buffers)是一种跨平台、语言无关、可扩大的序列化构造数据的办法,可用于网络数据交换及存储。

在序列化结构化数据的机制中,ProtoBuf 是灵便、高效、自动化的,绝对常见的 XML、JSON,形容同样的信息,ProtoBuf 序列化后数据量更小、序列化 / 反序列化速度更快、更简略。

一旦定义了要解决的数据的数据结构之后,就能够利用 ProtoBuf 的代码生成工具生成相干的代码。只需应用 Protobuf 对数据结构进行一次形容,即可利用各种不同语言 (proto3 反对 C ++, Java, Python, Go, Ruby, Objective-C, C#) 或从各种不同流中对你的结构化数据轻松读写。

二、为什么是 ProtoBuf

大家可能会感觉 Google 创造 ProtoBuf 是为了解决序列化速度的,其实实在的起因并不是这样的。

ProtoBuf 最先开始是 Google 用来解决索引服务器 request/response 协定的。没有 ProtoBuf 之前,Google 曾经存在了一种 request/response 格局,用于手动解决 request/response 的编解码。它也能反对多版本协定,不过代码不够优雅:

if (protocolVersion=1) {doSomething();
} else if (protocolVersion=2) {doOtherThing();
} ...

如果是十分明确的格式化协定,会使新协定变得非常复杂。因为开发人员必须确保申请发起者与解决申请的理论服务器之间的所有服务器都能了解新协定,而后能力切换开关以开始应用新协定。

这也就是每个服务器开发人员都遇到过的低版本兼容、新旧协定兼容相干的问题。

为了解决这些问题,于是 ProtoBuf 就诞生了。

ProtoBuf 最后被寄托以下 2 个特点:

  • 更容易引入新的字段,并且不须要检查数据的两头服务器能够简略地解析并传递数据,而无需理解所有字段。
  • 数据格式更加具备自我描述性,能够用各种语言来解决(C++, Java 等各种语言)。

这个版本的 ProtoBuf 仍须要本人手写解析的代码。

不过随着零碎缓缓倒退,演进,ProtoBuf 具备了更多的个性:

  • 主动生成的序列化和反序列化代码防止了手动解析的须要。(官网提供主动生成代码工具,各个语言平台的根本都有)。
  • 除了用于数据交换之外,ProtoBuf 被用作长久化数据的便捷自描述格局。

ProtoBuf 当初是 Google 用于数据交换和存储的通用语言。谷歌代码树中定义了 48162 种不同的音讯类型,包含 12183 个 .proto 文件。它们既用于 RPC 零碎,也用于在各种存储系统中长久存储数据。

ProtoBuf 诞生之初是为了解决服务器端新旧协定 (高下版本) 兼容性问题,名字也很体贴,“协定缓冲区”。只不过前期缓缓倒退成用于传输数据。

Protocol Buffers 命名由来:

Why the name “Protocol Buffers”?

The name originates from the early days of the format, before we had the protocol buffer compiler to generate classes for us. At the time, there was a class called ProtocolBuffer which actually acted as a buffer for an individual method. Users would add tag/value pairs to this buffer individually by calling methods like AddValue(tag, value). The raw bytes were stored in a buffer which could then be written out once the message had been constructed.

Since that time, the “buffers” part of the name has lost its meaning, but it is still the name we use. Today, people usually use the term “protocol message” to refer to a message in an abstract sense, “protocol buffer” to refer to a serialized copy of a message, and “protocol message object” to refer to an in-memory object representing the parsed message.

三、如何应用 ProtoBuf

3.1 ProtoBuf 协定的工作流程

能够看到,对于序列化协定来说,应用方只须要关注业务对象自身,即 idl 定义,序列化和反序列化的代码只须要通过工具生成即可。

3.2  ProtoBuf 音讯定义

ProtoBuf 的音讯是在 idl 文件 (.proto) 中形容的。上面是本次样例中应用到的音讯描述符 customer.proto:

syntax = "proto3";

package domain;

option java_package = "com.protobuf.generated.domain";
option java_outer_classname = "CustomerProtos";

message Customers {repeated Customer customer = 1;}

message Customer {
    int32 id = 1;
    string firstName = 2;
    string lastName = 3;

    enum EmailType {
        PRIVATE = 0;
        PROFESSIONAL = 1;
    }

    message EmailAddress {
        string email = 1;
        EmailType type = 2;
    }

    repeated EmailAddress email = 5;
}

下面的音讯比较简单,Customers 蕴含多个 Customer,Customer 蕴含一个 id 字段,一个 firstName 字段,一个 lastName 字段以及一个 email 的汇合。

除了这些定义外,文件顶部还有三行可帮忙代码生成器:

  1. 首先,syntax = “proto3″ 用于 idl 语法版本,目前有两个版本 proto2 和 proto3,两个版本语法不兼容,如果不指定,默认语法是 proto2。因为 proto3 比 proto2 反对的语言更多,语法更简洁,本文应用的是 proto3。
  2. 其次有一个 package domain; 定义。此配置用于嵌套生成的类 / 对象。
  3. 有一个 option java_package 定义。生成器还应用此配置来嵌套生成的源。此处的区别在于这仅实用于 Java。在应用 Java 创立代码和应用 JavaScript 创立代码时,应用了两种配置来使生成器的行为有所不同。也就是说,Java 类是在包 com.protobuf.generated.domain 下创立的,而 JavaScript 对象是在包 domain 下创立的。

ProtoBuf 提供了更多选项和数据类型,本文不做具体介绍,感兴趣能够参考这里。

3.3 代码生成

首先装置 ProtoBuf 编译器 protoc,这里有具体的装置教程,装置实现后,能够应用以下命令生成 Java 源代码:

protoc --java_out=./src/main/java ./src/main/idl/customer.proto

从我的项目的根门路执行该命令,并增加了两个参数:java_out,定义./src/main/java/ 为 Java 代码的输入目录;而./src/main/idl/customer.proto 是.proto 文件所在目录。

生成的代码非常复杂,然而侥幸的是它的用法却非常简单。

        CustomerProtos.Customer.EmailAddress email = CustomerProtos.Customer.EmailAddress.newBuilder()
                .setType(CustomerProtos.Customer.EmailType.PROFESSIONAL)
                .setEmail("crichardson@email.com").build();

        CustomerProtos.Customer customer = CustomerProtos.Customer.newBuilder()
                .setId(1)
                .setFirstName("Lee")
                .setLastName("Richardson")
                .addEmail(email)
                .build();
        // 序列化
        byte[] binaryInfo = customer.toByteArray();
        System.out.println(bytes_String16(binaryInfo));
        System.out.println(customer.toByteArray().length);
        // 反序列化
        CustomerProtos.Customer anotherCustomer = CustomerProtos.Customer.parseFrom(binaryInfo);
        System.out.println(anotherCustomer.toString());

3.4 性能数据

咱们简略地以 Customers 为模型,别离结构、选取小对象、一般对象、大对象进行性能比照。

序列化耗时以及序列化后数据大小比照

反序列化耗时

更多性能数据能够参考官网 Benchmark

四、总结

下面介绍了 ProtoBuf 是什么、产生的背景、根本用法,咱们再总结下。

长处:

1. 效率高

从序列化后的数据体积角度,与 XML、JSON 这类文本协定相比,ProtoBuf 通过 T -(L)-V(TAG-LENGTH-VALUE)形式编码,不须要 ”, {,}, : 等分隔符来结构化信息,同时在编码层面应用 varint 压缩,所以形容同样的信息,ProtoBuf 序列化后的体积要小很多,在网络中传输耗费的网络流量更少,进而对于网络资源缓和、性能要求十分高的场景,ProtoBuf 协定是不错的抉择。

// 咱们简略做个比照
// 要形容如下 JSON 数据
{"id":1,"firstName":"Chris","lastName":"Richardson","email":[{"type":"PROFESSIONAL","email":"crichardson@email.com"}]}
# 应用 JSON 序列化后的数据大小为 118byte
7b226964223a312c2266697273744e616d65223a224368726973222c226c6173744e616d65223a2252696368617264736f6e222c22656d61696c223a5b7b2274797065223a2250524f46455353494f4e414c222c22656d61696c223a226372696368617264736f6e40656d61696c2e636f6d227d5d7d
# 而应用 ProtoBuf 序列化后的数据大小为 48byte
0801120543687269731a0a52696368617264736f6e2a190a156372696368617264736f6e40656d61696c2e636f6d1001

从序列化 / 反序列化速度角度,与 XML、JSON 相比,ProtoBuf 序列化 / 反序列化的速度更快,比 XML 要快 20-100 倍。

2. 反对跨平台、多语言

ProtoBuf 是平台无关的,无论是 Android 与 PC,还是 C# 与 Java 都能够利用 ProtoBuf 进行无障碍通信。

proto3 反对 C ++, Java, Python, Go, Ruby, Objective-C, C#。

3. 扩展性、兼容性好

具备向后兼容的个性,更新数据结构当前,老版本仍旧能够兼容,这也是 ProtoBuf 诞生之初被寄托解决的问题。因为编译器对不辨认的新增字段会跳过不解决。

4. 应用简略

ProtoBuf 提供了一套编译工具,能够主动生成序列化、反序列化的样板代码,这样开发者只有关注业务数据 idl,简化了编码解码工作以及多语言交互的复杂度。

毛病:

可读性差,不足自描述

XML,JSON 是自描述的,而 ProtoBuf 则不是。

ProtoBuf 是二进制协定,编码后的数据可读性差,如果没有 idl 文件,就无奈了解二进制数据流,对调试不敌对。

不过 Charles 曾经反对 ProtoBuf 协定,导入数据的形容文件即可,详情可参考 Charles Protocol Buffers

此外,因为没有 idl 文件无奈解析二进制数据流,ProtoBuf 在肯定水平上能够爱护数据,晋升外围数据被破解的门槛,升高外围数据被盗爬的危险。

五、参考

  1. 维基百科
  2. 序列化与反序列化
  3. 官网 Benchmark
  4. Charles Protocol Buffers
  5. choose-protocol-buffers

作者:Li Guanyun

退出移动版