本文由vivo技术团队李冠云分享。为了提高阅读体验,进行了多次修改和重新编排。一、简介Protobuf作为一种跨平台、语言无关、可扩展的序列化结构化数据通信协议,已广泛应用于网络数据交换场景(如IM通信、分布式RPC调用等)。随着互联网的发展,分布式系统的异构性会越来越突出,跨语言的需求会越来越明显。同时,gRPC也很有可能取代Restful,而Protobuf是gRPC跨语言、高性能的法宝。我们的技术有必要深入了解Protobuf的原理,为以后的技术更新和选型打下基础。借此机会,我将个人的Protobuf学习过程和实践经验总结成一篇文章,与大家共同探讨学习。本文主要从Protobuf的基本概念入手,包括技术背景、技术原理、使用方法、优缺点。PS:本文与上一篇《Protobuf从入门到精通,一篇就够!》类似,适合作为Protobuf的入门文章。不过本文力求简洁,不涉及Protobuf的具体技术细节。目的是降低阅读门槛,提高阅读效果。有用。学习交流:移动IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》-开源IM框架源码:https://github.com/JackJiang2...(备用地址点此)(本文已同步发表于:http://www.52im.net/thread-40...)2.系列文章本文是系列文章的第二篇。本系列总目录如下:《IM通讯协议专题学习(一):Protobuf从入门到精通,一篇就够!》《IM通讯协议专题学习(二):快速理解Protobuf的背景、原理、使用、优缺点》(*本文)《IM通讯协议专题学习(三):由浅入深,从通信编解码原理上理解Protobuf》(稍后发布..)《IM通讯协议专题学习(四):从Base64到Protobuf,详解Protobuf的数据编码原理》(稍后发布..)《IM通讯协议专题学习(五):Protobuf到底比JSON快几倍?请看全方位实测!》(稍后发布..)《IM通讯协议专题学习(六):手把手教你如何在Android上从零使用Protobuf》(稍后发布..)《IM通讯协议专题学习(七):手把手教你如何在NodeJS中从零使用Protobuf》(稍后发布..)《IM通讯协议专题学习(八):金蝶随手记团队的Protobuf应用实践(原理篇)? 》(稍后发布..)《IM通讯协议专题学习(九):金蝶随手记团队的Protobuf应用实践(实战篇) 》(稍后发布..)3.什么是Protobuf?Protobuf(全称ProtocolBuffers)是一种跨平台、语言无关、可扩展的结构化数据序列化方法,可用于网络通信数据交换和存储。在序列化结构化数据的机制上,Protobuf灵活、高效、自动化。与普通的XML和JSON相比,它描述的是相同的信息。Protobuf序列化后数据量更小,序列化/反序列化速度更快。更简单。一旦定义了待处理数据的数据结构,就可以使用Protobuf的代码生成工具生成相关代码。只需使用Protobuf描述一次数据结构,你就可以使用各种语言(proto3支持C++、Java、Python、Go、Ruby、Objective-C、C#)或者将你的结构化数据从各种流中轻??松读写.PS:类似的介绍在之前的文章《Protobuf从入门到精通,一篇就够!》中也有介绍,有兴趣的可以一起看。4.为什么选择Protobuf?4.1技术背景你可能认为Google发明Protobuf是为了解决序列化速度问题,但真正的原因不是这样的。Protobuf最早被谷歌用来解决索引服务器请求/响应协议。在Protobuf之前,Google已经有了request/responseformat,用于手动处理request/response编解码。这种sstk风格也可以支持多版本协议,但是代码不够优雅:if(protocolVersion=1){doSomething();}elseif(protocolVersion=2){doOtherThing();}...如果是一个非常明确的格式协议,会使新协议变得非常复杂。因为开发人员必须确保请求发起者和处理请求的实际服务器之间的所有服务器都了解新协议,然后才能开始使用新协议。这是每个服务器开发者都遇到过的低版本兼容和新旧协议兼容问题。为了解决这些问题,Protobuf诞生了。4.2Protobuf的诞生Protobuf最初寄托在以下两个期望上:1)更容易引入新的字段,不需要校验数据的中间服务器可以简单的解析传递数据(不需要知道所有的字段);2)数据格式更具自描述性,可以用各种语言(如C++、Java等)进行处理。但是这个版本的Protobuf还是需要自己写解析代码。随着Protobuf的发展和演化,它拥有了更多的特性:1)自动生成序列化和反序列化代码(避免了需要手动解析,官方提供了代码自动生成工具,各个语言平台基本都有);2)Protobuf除了用于数据交换之外,还作为一些持久化数据的一种方便的自描述格式。ProtocolBuffers命名的由来:为什么叫“ProtocolBuffers”?这个名字起源于格式的早期,在我们有protocolbuffer编译器为我们生成类之前。当时有一个名为ProtocolBuffer的类,它实际上充当了单个方法的缓冲区。用户可以通过调用AddValue(tag,value)等方法将标记/值对单独添加到此缓冲区。原始字节存储在缓冲区中,一旦消息被构建,缓冲区就可以被写出。从那时起,名称中的“缓冲区”部分就失去了意义,但它仍然是我们使用的名称。今天,人们通常使用术语“协议消息”来指代抽象意义上的消息,“协议缓冲区”指代消息的序列化副本,“协议消息对象”指代表示的内存中对象4.3Protobuf在谷歌业务中的位置Protobuf现在是Google用于数据交换和存储的通用语言。Google代码树中定义了48162种不同的消息类型,包括12183个.proto文件。它们既用于RPC系统,也用于在各种存储系统中持久保存数据。Protobuf的诞生是为了解决服务端新旧协议(高低版本)的兼容问题,名字也很贴心——“协议缓冲区”,但后期逐渐发展成一种数据传输.5.Protobuf协议的工作原理如下图所示:可以看出,对于序列化协议,用户只需要关注业务对象本身,即idl定义,代码为序列化和反序列化只需要工具生成即可。能。6.Protobuf协议的消息定义Protobuf的消息在idl文件(.proto)中描述。以下是本例中使用的消息描述符customer.proto:syntax="proto3";包域;选项java_package="com.Protobuf.generated.domain";选项java_outer_classname="CustomerProtos";messageCustomers{repeatedCustomercustomer=1;}messageCustomer{int32id=1;字符串名字=2;字符串姓氏=3;枚举EmailType{PRIVATE=0;专业=1;}消息电子邮件地址{字符串电子邮件=1;电子邮件类型类型=2;}repeatedEmailAddressemail=5;}上面的消息比较简单,Customers包含多个Customers(Customer包含一个id字段,一个firstName字段,一个lastName字段和一个email的集合)。除了以上定义,文件顶部还有三个声明可以帮助代码生成器:1)syntax="proto3":用于idl语法版本,目前有proto2和proto3两个版本,语法两个版本的不兼容,如果不指定默认语法为proto2(因为proto3支持的语言比proto2多,而且语法更简洁,本文使用proto3);2)包域:该配置用于嵌套生成的类/对象;3)optionjava_package:生成器也使用这个配置来嵌套生成的源(这里的区别是这只适用于Java,两个配置用于使生成器在Java中创建代码与在JavaScript中创建代码时表现不同。那即Java类创建在com.Protobuf.generated.domain包下,JavaScript对象创建在包domain下)。Protobuf提供了更多选项和数据类型。本文不对它们进行详细介绍。有兴趣的可以参考官方文档。7.Protobuf代码生成首先安装Protobuf编译器protoc(详细安装教程点这里)。安装完成后,可以使用如下命令生成Java源码:1protoc--java_out=./src/main/java./src/main/idl/customer.proto上面命令的作用是:执行项目根路径下的命令,添加两个参数java_out(即定义./src/main/java/为Java代码的输出目录;./src/main/idl/customer.proto为.proto文件所在的目录)。生成的代码非常复杂,但幸运的是它的用法非常简单:com").build();CustomerProtos.Customercustomer=CustomerProtos.Customer.newBuilder().setId(1).setFirstName("Lee").setLastName("Richardson").addEmail(email).bud(;//序列化byte[]binaryInfo=customer.toByteArray();System.out.println(bytes_String16(binaryInfo));System.out.println(customer.toByteArray().length);//反序列化CustomerProtos.CustomeranotherCustomer=CustomerProtos.Customer.parseFrom(binaryInfo);System.out.println(anotherCustomer.toString());8.Protobuf性能数据我们简单的以上面的Customers为模型,构造并选取smallobjects,commonobjects,Largeobjects进行性能比较。序列化耗时和数据大小比较后serialization:反序列化耗时:更多性能数据参考官方测试Benchmark9.Protobuf的优势9.1效率高从序列化数据量来看,相比XML、JSON等文本协议,Protobuf采用T编码-(L)-V(TAG-LENGTH-VALUE)并且不需要"、{、}、:和其他分隔符来构造信息。同时在编码层面使用varint压缩。因此,描述同样的信息,Protobuf序列化后的体积要小很多,在网络中传输消耗的网络流量也少,也适用于网络资源紧张、性能要求非常高的场景。比如在移动网络下的IM即时通讯应用中,Protobuf协议就是一个非常好的选择(PS:这也是我开始分享Protobuf系列文章的原因)。让我们做一个简单的比较。描述以下JSON数据:1{"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倍。9.2支持跨平台多语言Protobuf是平台无关的,无论是Android、iOS、PC,还是C#和Java,都可以使用Protobuf进行无障碍通信。proto3支持C++、Java、Python、Go、Ruby、Objective-C和C#(详见《Protobuf从入门到精通,一篇就够》)。9.3扩展性和兼容性好Protobuf具有向后兼容的特点:更新数据结构后,旧版本仍然兼容。这也是Protobuf在诞生之初就被委托解决的问题,因为编译器会跳过它不认识的新字段。不要处理它。9.4简单易用Protobuf提供了一套编译工具,可以自动生成序列化和反序列化样板代码,让开发者只需要关注业务数据idl,简化了编解码工作和多语言交互的复杂性。10.Protobuf的缺点Protobuf的优点非常突出,但是缺点也很明显。Protobuf的主要缺点是:1)没有自描述能力:相对于XML和JSON,这两个是自描述的,而ProtoBuf不是;2)数据可读性很差:ProtoBuf是二进制协议,如果没有idl文件,无法理解二进制数据流,对调试很不友好。但是:Charles已经支持Protobuf协议,导入数据描述文件即可。详情请参考CharlesProtocolBuffers。但是:由于没有idl文件,无法解析二进制数据流,ProtoBuf可以在一定程度上保护数据,提高核心数据被破解的门槛,降低核心数据被窃取和爬取的风险(是也是劣势变优势的典型例子)。11.参考资料[1]Protobuf官网[2]Protobuf从入门到精通,一篇就够了![3]如何选择即时通讯应用的数据传输格式[4]强烈推荐使用Protobuf作为你的即时通讯应用的数据传输格式[5]APP与后台通信数据格式的演变:从文本protocoltobinaryprotocol[6]面试必考,详解史上最流行的endian字节序[7]移动IM开发需要面对的技术问题(含通信协议选择)[8]简述移动IM开发的陷阱:架构设计、通信协议理论联系实际与客户端[9]:一套典型的IM通信协议设计详解[10]58家实时消息系统协议设计等技术实践分享(本文已同步发表于:http://www.52im.net/thread-40...)
