首页 > 技术文章 > 大数据 --> ProtoBuf的使用和原理

jeakeven 2016-04-23 12:01 原文

ProtoBuf的使用和原理

一、简介

  Protobuf是一个灵活的、高效的用于序列化数据的协议。相比较XML和JSON格式,protobuf更小、更快、更便捷。Protobuf是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以编译成Java、python、C++等代码,然后就可以直接使用,不需要再写其他代码,自带有解析的代码。一条消息数据,用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一。
 

二、安装

1、下载代码,https://github.com/google/protobuf
2、安装protobuf
tar -xzf protobuf-2.1.0.tar.gz 
cd protobuf
./configure --prefix=/usr/local/protobuf
make
make check
make install

3、配置文件

1)vim /etc/profile 和 ~/.profile 中添加:
  export PATH=$PATH:/usr/local/protobuf/bin/
  export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
2)配置动态链接库,vim /etc/ld.so.conf,在文件中添加/usr/local/protobuf/lib(注意: 在新行处添加)
3)执行:ldconfig

 

三、类似技术对比

1、优点

  1)Protobuf同XML相比,主要优点在于性能高。它以高效的二进制方式存储,比XML小3到10倍,快20到100倍。
  2)可以自定义数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
  3)“向后”兼容性好,用户不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。
  4)Protobuf语义更清晰,无需类似XML解析器的东西。Protobuf 编译器会将.proto文件编译生成对应的数据访问类以对Protobuf数据进行序列化、反序列化操作。
  5)使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。
 

2、不足

  1)Protbuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。
  2)XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。
  3)由于文本并不适合用来描述数据结构,所以 Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。
  4)由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容。
 

3、举例对比

protobuf和xml存入数据:
//在XML中建模Person的name和email字段:
<person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
</person>

//ProtocolBuffer的文本表示:
person {
    name: "John Doe"
    email: "jdoe@example.com"
}

读取数据:

//操作ProtocolBuffer也很简单:
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

//而XML的你需要:
cout << "Name: " << person.getElementsByTagName("name")->item(0)->innerText() << endl;
cout << "E-mail: " << person.getElementsByTagName("email")->item(0)->innerText() << end;

 

四、使用场景

  1、需要和其它系统做消息交换的,对消息大小很敏感的,那么protobuf适合了,它语言无关,消息空间相对xml和json等节省很多。
  2、小数据的场合。如果你是大数据,用它并不适合。
  3、项目语言是c++,java,python的,因为它们可以使用google的源生类库,序列化和反序列化的效率非常高。其他语言需要第三方或者自己写,序列化和反序列化的效率不保证。
 
 

五、程序示例(C++版)

     该程序示例的大致功能是,定义一个Persion结构体和存放Persion的AddressBook,然后一个写程序向一个文件写入该结构体信息,另一个程序从文件中读出该信息并打印到输出中。
1、address.proto文件
package tutorial;

message Persion {
    required string name = 1;
    required int32 age = 2;
}

message AddressBook {
    repeated Persion persion = 1;
}
编译.proto文件,执行命令: protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto,示例中执行命令protoc --cpp_out=/tmp addressbook.proto ,会在/tmp中生成文件addressbook.pb.h和addressbook.pb.cc。
 
2、write.cpp文件,向文件中写入AddressBook信息,该文件是二进制的
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"

using namespace std;

void PromptForAddress(tutorial::Persion *persion) {
    cout << "Enter persion name:" << endl;
    string name;
    cin >> name;
    persion->set_name(name);

    int age;
    cin >> age;
    persion->set_age(age);
}

int main(int argc, char **argv) {
    //GOOGLE_PROTOBUF_VERIFY_VERSION;
    if (argc != 2) {
        cerr << "Usage: " << argv[0] << " ADDRESS_BOOL_FILE" << endl;
        return -1;
    }
    tutorial::AddressBook address_book;
    {
        fstream input(argv[1], ios::in | ios::binary);
        if (!input) {
            cout << argv[1] << ": File not found. Creating a new file." << endl;
        }
        else if (!address_book.ParseFromIstream(&input)) {
            cerr << "Filed to parse address book." << endl;
            return -1;
        }
    }
    // Add an address
    PromptForAddress(address_book.add_persion());
    {
        fstream output(argv[1], ios::out | ios::trunc | ios::binary);
        if (!address_book.SerializeToOstream(&output)) {
            cerr << "Failed to write address book." << endl;
            return -1;
        }
    }
    // Optional: Delete all global objects allocated by libprotobuf.
    //google::protobuf::ShutdownProtobufLibrary();

    return 0;
}
编译write.cpp文件,执行命令:g++ addressbook.pb.cc write.cpp -o write `pkg-config --cflags --libs protobuf` ,注意,这里的`符号在键盘数字1键左边,也就是和~是同一个按键。
 
3、read.cpp文件,从文件中读出AddressBook信息并打印
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"

using namespace std;

void ListPeople(const tutorial::AddressBook& address_book) {
    for (int i = 0; i < address_book.persion_size(); i++) {
        const tutorial::Persion& persion = address_book.persion(i);

        cout << persion.name() << " " << persion.age() << endl;
    }
}

int main(int argc, char **argv) {
    //GOOGLE_PROTOBUF_VERIFY_VERSION;

    if (argc != 2) {
        cerr << "Usage: " << argv[0] << " ADDRESS_BOOL_FILE" << endl;
        return -1;
    }

    tutorial::AddressBook address_book;

    {
        fstream input(argv[1], ios::in | ios::binary);
        if (!address_book.ParseFromIstream(&input)) {
            cerr << "Filed to parse address book." << endl;
            return -1;
        }
        input.close();
    }

    ListPeople(address_book);

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

    return 0;
}
编译read.cpp文件,g++ addressbook.pb.cc read.cpp -o read `pkg-config --cflags --libs protobuf`
 
4、执行程序结果
 
 
ref:
http://www.cnblogs.com/luoxn28/p/5303517.html
http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html#resources
 
 
 

推荐阅读