`
haohappy2
  • 浏览: 326411 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Protobuf:A New style of data transfer

阅读更多

  首先,protobuf是一个开源项目(官方站点在“这里 ”),而且是后台很硬的开源项目。网上现有的大部分(至少80%)开源项目,要么是某人单干、要么是几个闲杂人等合伙搞。而protobuf则不然,它是鼎鼎大名的Google公司开发出来,并且在Google内部久经考验的一个东东。由此可见,它的作者绝非一般闲杂人等可比。
  那这个听起来牛X的东东到底有啥用处捏?简单地说,这个东东干的事儿其实和XML差不多,也就是把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议格式等场合。有同学可能心理犯嘀咕了:放着好好的XML不用,干嘛重新发明轮子啊?!先别急,后面俺自然会有说道。
  话说到了去年(大约是08年7 月),Google突然大发慈悲,把这个好东西贡献给了开源社区。这下,像俺这种喜欢捡现成的家伙可就有福啦!貌似喜欢捡现成的家伙还蛮多滴,再加上Google的号召力,开源后不到一年,protobuf的人气就已经很旺了。所以俺为了与时俱进,就单独开个帖子来忽悠一把。

  ◇性能好/效率高
  现在,俺就来说说Google公司为啥放着好端端的XML不用,非要另起炉灶,重新造轮子。一个根本的原因是XML性能不够好。
  先说时间开销:XML格式化(序列化)的开销倒还好;但是XML解析(反序列化)的开销就不敢恭维啦。俺之前经常碰到一些时间性能很敏感的场合,由于不堪忍受XML解析的速度,弃之如敝履。
  再来看空间开销:熟悉XML语法的同学应该知道,XML格式为了有较好的可读性,引入了一些冗余的文本信息。所以空间开销也不是太好(不过这点缺点,俺不常碰到)。
  由于Google公司赖以吹嘘的就是它的海量数据和海量处理能力。对于几十万、上百万机器的集群,动不动就是PB级的数据量,哪怕性能稍微提高0.1%也是相当可观滴。所以Google自然无法容忍XML在性能上的明显缺点。再加上Google从来就不缺造轮子的牛人,所以protobuf也就应运而生了。
  Google对于性能的偏执,那可是出了名的。所以,俺对于Google搞出来protobuf是非常滴放心,性能上不敢说是最好,但肯定不会太差。

  ◇代码生成机制
  除了性能好,代码生成机制是主要吸引俺的地方。为了说明这个代码生成机制,俺举个例子。
  比如有个电子商务的系统(假设用C++实现),其中的模块A需要发送大量的订单信息给模块B,通讯的方式使用socket。
假设订单包括如下属性:
--------------------------------
  时间:time(用整数表示)
  客户id:userid(用整数表示)
  交易金额:price(用浮点数表示)
  交易的描述:desc(用字符串表示)
--------------------------------
  如果使用protobuf实现,首先要写一个proto文件(不妨叫Order.proto),在该文件中添加一个名为"Order"的message结构,用来描述通讯协议中的结构化数据。该文件的内容大致如下:


--------------------------------
message Order
{
  required int32 time = 1;
  required int32 userid = 2;
  required float price = 3;
  optional string desc = 4;
}
--------------------------------


  然后,使用protobuf内置的编译器编译 该proto。由于本例子的模块是C++,你可以通过protobuf编译器的命令行参数(看“这里 ”),指定它生成C++语言的“订单包装类”。(一般来说,一个message结构会生成一个包装类)
  然后你使用类似下面的代码来序列化/解析该订单包装类:


--------------------------------
// 发送方
Order order;
order.set_time(XXXX);
order.set_userid(123);
order.set_price(100.0f);
order.set_desc("a test order");

string sOrder;
order.SerailzeToString(&sOrder);
// 然后调用某种socket的通讯库把序列化之后的字符串发送出去
// ......

--------------------------------
// 接收方
string sOrder;
// 先通过网络通讯库接收到数据,存放到某字符串sOrder
// ......

Order order;
if(order.ParseFromString(sOrder)) // 解析该字符串
{
  cout << "userid:" << order.userid() << endl
  << "desc:" << order.desc() << endl;
}
else
{
  cerr << "parse error!" << endl;
}
--------------------------------


  有了这种代码生成机制,开发人员再也不用吭哧吭哧地编写那些协议解析的代码了(干这种活是典型的吃力不讨好)。
  万一将来需求发生变更,要求给订单再增加一个“状态”的属性,那只需要在Order.proto文件中增加一行代码。对于发送方(模块A),只要增加一行设置状态的代码;对于接收方(模块B)只要增加一行读取状态的代码。哇塞,简直太轻松了!
  另外,如果通讯双方使用不同的编程语言来实现,使用这种机制可以有效确保两边的模块对于协议的处理是一致的。
  顺便跑题一下。
  从某种意义上讲,可以把proto文件看成是描述通讯协议的规格说明书(或者叫接口规范)。这种伎俩其实老早就有了,搞过微软的COM编程或者接触过CORBA的同学,应该都能从中看到IDL(详细解释看“这里 ”)的影子。它们的思想是相通滴。

  ◇支持“向后兼容”和“向前兼容”
  还是拿刚才的例子来说事儿。为了叙述方便,俺把增加了“状态”属性的订单协议成为“新版本”;之前的叫“老版本”。
  所谓的“向后兼容”(backward compatible),就是说,当模块B升级了之后,它能够正确识别模块A发出的老版本的协议。由于老版本没有“状态”这个属性,在扩充协议时,可以考虑把“状态”属性设置成非必填 的,或者给“状态”属性设置一个缺省值(如何设置缺省值,参见“这里 ”)。
  所谓的“向前兼容”(forward compatible),就是说,当模块A升级了之后,模块B能够正常识别模块A发出的新版本的协议。这时候,新增加的“状态”属性会被忽略。
  “向后兼容”和“向前兼容”有啥用捏?俺举个例子:当你维护一个很庞大的分布式系统时,由于你无法同时 升级所有 模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。

  ◇支持多种编程语言
  俺开博以来点评的几个开源项目(比如“Sqlite ”、“cURL ”),都是支持很多种 编程语言滴,这次的protobuf也不例外。在Google官方发布的源代码中包含了C++、Java、Python三种语言(正好也是俺最常用的三种,真爽)。如果你平时用的就是这三种语言之一,那就好办了。
  假如你想把protobuf用于其它语言,咋办捏?由于Google一呼百应的号召力,开源社区对protobuf响应踊跃,近期冒出很多其它编程语言的版本(比如ActionScript、C#、Lisp、Erlang、Perl、PHP、Ruby等),有些语言还同时搞出了多个开源的项目。具体细节可以参见“这里 ”。
  不过俺有义务提醒一下在座的各位同学。如果你考虑把protobuf用于上述这些语言,一定认真评估对应的开源库。因为这些开源库不是Google官方提供的、而且出来的时间还不长。所以,它们的质量、性能等方面可能还有欠缺。

  ★protobuf有啥缺陷?
  前几天刚刚在“光环效应 ”的帖子里强调了“要同时评估优点和缺点”。所以俺最后再来批判一下这玩意儿的缺点。
  ◇应用不够广
  由于protobuf刚公布没多久,相比XML而言,protobuf还属于初出茅庐。因此,在知名度、应用广度等方面都远不如XML。由于这个原因,假如你设计的系统需要提供若干对外的接口给第三方系统调用,俺奉劝你暂时不要考虑protobuf格式。
  ◇二进制格式导致可读性差
  为了提高性能,protobuf采用了二进制格式进行编码。这直接导致了可读性差的问题(严格地说,是没有可读性)。虽然protobuf提供了TextFormat这个工具类(文档在“这里 ”),但终究无法彻底解决此问题。
  可读性差的危害,俺再来举个例子。比如通讯双方如果出现问题,极易导致扯皮(都不承认自己有问题,都说是对方的错)。俺对付扯皮的一个简单方法就是直接抓包并dump成log,能比较容易地看出错误在哪一方。但是protobuf的二进制格式,导致你抓包并直接dump出来的log难以看懂。
  ◇缺乏自描述
  一般来说,XML是自描述的,而protobuf格式则不是。给你一段二进制格式的协议内容,如果不配合相应的proto文件,那简直就像天书一般。
  由于“缺乏自描述”,再加上“二进制格式导致可读性差”。所以在配置文件方面,protobuf是肯定无法取代XML的地位滴。

  ★为什么俺会用上protobuf?
  试用google Protocol Buffers( 比xml快20~100倍, 支持序列化数据 )使用指南可以看这篇文章介绍:
http://www.cppblog.com/liquidx/

下载Protocol Buffers:
http://code.google.com/p/protobuf/downloads/list

在vc环境下使用则在解压缩文件中有一个vsprojects文件夹, 使用vs来编译出libprotobuf.lib,libprotoc.lib
设置你的扩展头文件包含目录为 "D:\protobuf-2.1.0\src"

按照指南, 首先我们定义一个test.proto文件内容如下:


package Test;

message Person {
        required string name = 1;
        required int32 id = 2;
        optional string email = 3;
}


然后用protoc编译器编译出c++模块, 这里有一个已经编译好的编译器, 你也可以从压缩包中的源代码编译出该编译器.
http://protobuf.googlecode.com/files/protoc-2.1.0-win32.zip

用这个指令编译
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/test.proto
然后我们得到了
test.pb.h
test.pb.cc
2个c++文件

现在我们可以在项目中使用它了:

 

#include <iostream>
#include "test.pb.h"
#include <fstream>

#pragma comment( lib, "libprotobuf.lib" )
#pragma comment( lib, "libprotoc.lib" )

int _tmain(int argc, _TCHAR* argv[])
{
    // Verify that the version of the library that we linked against is
    // compatible with the version of the headers we compiled against.
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    // 设置数据, 并序列化到文件
    Test::Person person;
    person.set_id( 123 );
    person.set_name( "liquidx" );
    person.set_email( "liquidx@163.com" );

    std::fstream out( "person.pb", std::ios::out | std::ios::binary | std::ios::trunc );
    person.SerializeToOstream( &out );
    out.close();

    // 从文件中读取数据, 并且反序列化
    Test::Person person1;
    std::fstream in( "person.pb", std::ios::in | std::ios::binary );
    if ( !person1.ParseFromIstream( &in ) ) {
      std::cerr << "Failed to parse person.pb." << std::endl;
      exit(1);
    }

    std::cout << "ID: " << person1.id() << std::endl;
    std::cout << "name: " << person1.name() << std::endl;
    if ( person1.has_email() ) {
      std::cout << "e-mail: " << person1.email() << std::endl;
    }

    // Optional:  Delete all global objects allocated by libprotobuf.
    google::protobuf::ShutdownProtobufLibrary();

    getchar();
    return 0;
}


输出:
ID : 123
name : liquidx
e-mail : liquidx@163.com

产生的person.pb内容如下(28字节):
liquidx{liquidx@163.com

试用完毕:
感觉Protocol Buffers挺好用的, 项目的某些xml部分可以使用它来替代,这样在数据读取和操作上比xml更加方便直接, 且效率高效!
用它也可以在网络处理上得到一些好处!

 

分享到:
评论

相关推荐

    protobuf-java-3.11.4-API文档-中文版.zip

    Maven坐标:com.google.protobuf:protobuf-java:3.11.4; 标签:google、protobuf、java、中文文档、jar包; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档...

    compile_protobuf_protobuf:compile_protobuf_

    标题"compile_protobuf_protobuf:compile_protobuf_"指的是使用protobuf编译器来处理.proto文件的过程。这个过程涉及到将.proto文件编译成特定编程语言(如C++、Java或Python)的源代码,以便在程序中使用protobuf...

    protobuf-java-3.16.1-API文档-中文版.zip

    Maven坐标:com.google.protobuf:protobuf-java:3.16.1; 标签:protobuf、java、google、jar包、java、中文文档; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译...

    6.Json与Protocolbuf相互转换1

    在示例代码中,可以看到使用了`google::protobuf::util::MessageToJsonString`和`google::protobuf::util::JsonToMessage`这两个函数来实现这个转换过程。 首先,让我们深入了解一下 ProtoBuf 的基本概念。ProtoBuf...

    protobuf-java-3.11.4-API文档-中英对照版.zip

    Maven坐标:com.google.protobuf:protobuf-java:3.11.4; 标签:google、protobuf、java、中英对照文档、jar包; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,...

    protobuf-java-3.7.1-API文档-中文版.zip

    Maven坐标:com.google.protobuf:protobuf-java:3.7.1; 标签:google、protobuf、java、中文文档、jar包; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档...

    protobuf-cpp-3.16.0-rc-1-源码+已编译的静态库.zip

    protobuf-cpp-3.16.0-rc-1-源码+已编译的静态库.zip 是一个针对Qt5.9和Windows环境的Protocol Buffers(简称protobuf)的资源包,包含了protobuf 3.16.0版本的源代码以及预编译的静态库。Protocol Buffers是Google...

    caffe python error: No module named google.protobuf.internal

    `google.protobuf`实际上是Protocol Buffers(简称Protobuf)库的一部分,这是一个由Google开发的用于序列化结构化数据的灵活、高效且自动化的机制。在Caffe中,Protobuf被广泛用于定义网络架构、训练参数等配置信息...

    protobuf 3.5.1

    protobuf-all-3.5.1.tar.gz protobuf-cpp-3.5.1.tar.gz protobuf-csharp-3.5.1.tar.gz protobuf-java-3.5.1.tar.gz protobuf-js-3.5.1.tar.gz protobuf-objectivec-3.5.1.tar.gz protobuf-php-3.5.1.tar.gz protobuf...

    google protobuf-2.5.0源码包,内含安装步骤

    (内附源码编译文档)Protocol buffers是一个灵活的、高效的、自动化的用于对结构化数据进行序列化的协议,与XML相比,Protocol buffers序列化后的码流更小、速度更快、操作更简单。

    protobuf-java-3.2.0.jar

    protobuf-java-3.2.0.jar

    google protobuf-中英文使用手册

    **protobuf简介** Protocol Buffers(简称protobuf)是Google开发的一种数据序列化协议,它提供了一种灵活、高效、自动化的结构化数据序列化方法,类似于XML、JSON等格式,但更小、更快、更简单。protobuf可以用于...

    egret_protobuf:egret白鹭引擎protobuf 静态解析使用 TS中使用protobuf

    const player = new protobuf.Player(); player.id = 1; player.name = 'Alice'; player.score = 1000; const encodedData = GameServer.encodePlayer(player); egret.WebSocket.send(encodedData); ``` 通过这样...

    google protobuf-3.2.0 windowsprotoc3.2.0.exe

    protobuf全称是Protocol Buffers,是由Google开发的一种数据序列化协议,它允许开发者定义数据结构,然后将数据编码为二进制格式,用于存储或网络传输。标题中的"google protobuf-3.2.0"指的是protobuf的3.2.0版本,...

    protobuf vs版本

    标题中的“protobuf vs版本”指的是Protocol Buffers(protobuf)在Visual Studio环境下的编译版本问题。Protocol Buffers是由Google开发的一种数据序列化协议,它允许结构化数据串行化,可用于数据存储、通信协议等...

    mapstruct-spi-protobuf:Mapstruct的Protobuf访问器命名策略

    用于协议缓冲区的Mapstruct SPI实现 这种命名策略有助于在您的域类和protobuf类之间生成有效的映射代码。 无论和类支载。 注意:取决于mapstruct 1.4.0.CR1ProtobufAccessor命名策略扩展...

    android studio Kotlin中使用 GRPC和protobuf

    artifact = 'com.google.protobuf:protoc:3.x.x' } generateProtoTasks { all().each { task -&gt; task.builtins { java { option 'lite' } kotlin { option 'lite' } } } } } ``` 然后,定义protobuf消息...

    protobuf解析器-3.9.0.rar

    protobuf(Protocol Buffers)是Google开发的一种数据序列化协议,它是跨平台的,可以用于结构化数据的序列化,类似于XML、JSON等,但更小、更快、更简单。protobuf的解析器是用于将.proto文件转换为编程语言的源...

    google protobuf-2.5.0

    protobuf-2.5.0是Google Protocol Buffers(简称protobuf)的一个版本,它是一个高效、灵活的数据序列化库,常用于构建跨平台的通信协议和数据存储格式。Protocol Buffers是一种语言中立、平台中立的机制,可以将...

    Protobuf:Protobuf使用

    Protobuf 前言 protobuf,全称:Google Protocol Buffer,是Google开源的一种轻便高效的结构化数据存储格式,可以用于结构化数据的串行化,也称作序列化,主要用于数据存储或是RPC数据交换,支持多语言,可拓展。 ...

Global site tag (gtag.js) - Google Analytics