Google 開源技術protobuf

http://blog.csdn.net/hguisu/article/details/20721109#0-tsina-1-1601-397232819ff9a47a7b7e80a40613cfe1php

1.  Protobuf簡介html

       protobuf是google提供的一個開源序列化框架,相似於XML,JSON這樣的數據表示語言,其最大的特色是基於二進制,所以比傳統的XML 表示高效短小得多。雖然是二進制數據格式,但並無所以變得複雜,開發人員經過按照必定的語法定義結構化的消息格式,而後送給命令行工具,工具將自動生成 相關的類,能夠支持php、java、c++、python等語言環境。經過將這些類包含在項目中,能夠很輕鬆的調用相關方法來完成業務消息的序列化與反 序列化工做。java

     protobuf在google中是一個比較核心的基礎庫,做爲分佈式運算涉及到大量的不一樣業務消息的傳遞,如何高效簡潔的表示、操做這些業務消息在 google這樣的大規模應用中是相當重要的。而protobuf這樣的庫正好是在效率、數據大小、易用性之間取得了很好的平衡。python

 

2.  Protobuf如何工做ios

        你首先須要在一個 .proto 文件中定義你須要作串行化的數據結構信息。每一個ProtocolBuffer信息是一小段邏輯記錄,包含一系列的鍵值對。這裏有個很是簡單的 .proto 文件定義了我的信息:c++

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

    enum PhoneType {
        MOBILE=0;
        HOME=1;
        WORK=2;
    }

    message PhoneNumber {
        required string number=1;
        optional PhoneType type=2 [default=HOME];
    }

    repeated PhoneNumber phone=4;
}

有如你所見,消息格式很簡單,每一個消息類型擁有一個或多個特定的數字字段,每一個字段擁有一個名字和一個值類型。值類型能夠是數字(整數或浮點)、布爾型、 字符串、原始字節或者其餘ProtocolBuffer類型,還容許數據結構的分級。你能夠指定可選字段,必選字段和重複字段。你能夠在( http://code.google.com/apis/protocolbuffers/docs/proto.html )找到更多關於如何編寫 .proto 文件的信息。apache

一旦你定義了本身的報文格式(message),你就能夠運行ProtocolBuffer編譯器,將你的 .proto 文件編譯成特定語言的類。這些類提供了簡單的方法訪問每一個字段(像是 query() 和 set_query() ),像是訪問類的方法同樣將結構串行化或反串行化。例如你能夠選擇C++語言,運行編譯如上的協議文件生成類叫作 Person 。隨後你就能夠在應用中使用這個類來串行化的讀取報文信息。你能夠這麼寫代碼:api

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream.output("myfile",ios::out | ios::binary);
person.SerializeToOstream(&output);

而後,你能夠讀取報文中的數據:數組

fstream input("myfile",ios::in | ios:binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

你能夠在不影響向後兼容的狀況下隨意給數據結構增長字段,舊有的數據會忽略新的字段。因此若是使用ProtocolBuffer做爲通訊協議,你能夠無須擔憂破壞現有代碼的狀況下擴展協議。服務器

你能夠在API參考( http://code.google.com/apis/protocolbuffers/docs/reference/overview.html )中找到完整的參考,而關於ProtocolBuffer的報文格式編碼則能夠在( http://code.google.com/apis/protocolbuffers/docs/encoding.html )中找到。


 

3.  Protobuf消息定義

      要通訊,必須有協議,不然雙方沒法理解對方的碼流。在protobuf中,協議是由一系列的消息組成的。所以最重要的就是定義通訊時使用到的消息格式。

 

消息由至少一個字段組合而成,相似於C語言中的結構。每一個字段都有必定的格式。

字段格式:限定修飾符① | 數據類型② | 字段名稱③ | = | 字段編碼值④ | [字段默認值⑤]

①.限定修飾符包含 required\optional\repeated

 

Required: 表示是一個必須字段,必須相對於發送方,在發送消息以前必須設置該字段的值,對於接收方,必須可以識別該字段的意思。發送以前沒有設置required字段或者沒法識別required字段都會引起編解碼異常,致使消息被丟棄。

Optional:表示是一個可選字段,可選對於發送方,在發送消息時,能夠有選擇性的設置或者不設置該字段的值。對於接收方,若是可以識別可選字段就進 行相應的處理,若是沒法識別,則忽略該字段,消息中的其它字段正常處理。---由於optional字段的特性,不少接口在升級版本中都把後來添加的字段 都統一的設置爲optional字段,這樣老的版本無需升級程序也能夠正常的與新的軟件進行通訊,只不過新的字段沒法識別而已,由於並非每一個節點都須要 新的功能,所以能夠作到按需升級和平滑過渡。

Repeated:表示該字段能夠包含0~N個元素。其特性和optional同樣,可是每一次能夠包含多個值。能夠看做是在傳遞一個數組的值。

 

②.數據類型

 

Protobuf定義了一套基本數據類型。幾乎均可以映射到C++\Java等語言的基礎數據類型.

      

protobuf 數據類型

描述

打包

C++語言映射

bool

布爾類型

1字節

bool

double

64位浮點數

N

double

float

32爲浮點數

N

float

int32

32位整數、

N

int

uin32

無符號32位整數

N

unsigned int

int64

64位整數

N

__int64

uint64

64爲無符號整

N

unsigned __int64

sint32

32位整數,處理負數效率更高

N

int32

sing64

64位整數 處理負數效率更高

N

__int64

fixed32

32位無符號整數

4

unsigned int32

fixed64

64位無符號整數

8

unsigned __int64

sfixed32

32位整數、能以更高的效率處理負數

4

unsigned int32

sfixed64

64爲整數

8

unsigned __int64

string

只能處理 ASCII字符

N

std::string

bytes

用於處理多字節的語言字符、如中文

N

std::string

enum

能夠包含一個用戶自定義的枚舉類型uint32

N(uint32)

enum

message

能夠包含一個用戶自定義的消息類型

N

object of class

N 表示打包的字節並非固定。而是根據數據的大小或者長度。

例如int32,若是數值比較小,在0~127時,使用一個字節打包。

關於枚舉的打包方式和uint32相同。

關於message,相似於C語言中的結構包含另一個結構做爲數據成員同樣。

關於 fixed32 和int32的區別。fixed32的打包效率比int32的效率高,可是使用的空間通常比int32多。所以一個屬於時間效率高,一個屬於空間效率高。 根據項目的實際狀況,通常選擇fixed32,若是遇到對傳輸數據量要求比較苛刻的環境,能夠選擇int32.

③.字段名稱

 

字段名稱的命名與C、C++、Java等語言的變量命名方式幾乎是相同的。

protobuf建議字段的命名採用如下劃線分割的駝峯式。例如 first_name 而不是firstName.

④.字段編碼值

有了該值,通訊雙方纔能互相識別對方的字段。固然相同的編碼值,其限定修飾符和數據類型必須相同。

編碼值的取值範圍爲 1~2^32(4294967296)。

其中 1~15的編碼時間和空間效率都是最高的,編碼值越大,其編碼的時間和空間效率就越低(相對於1-15),固然通常狀況下相鄰的2個值編碼效率的是相同的,除非2個值剛好實在4字節,12字節,20字節等的臨界區。好比15和16.

1900~2000編碼值爲Google protobuf 系統內部保留值,建議不要在本身的項目中使用。

protobuf 還建議把常常要傳遞的值把其字段編碼設置爲1-15之間的值。

消息中的字段的編碼值無需連續,只要是合法的,而且不能在同一個消息中有字段包含相同的編碼值。

建議:項目投入運營之後涉及到版本升級時的新增消息字段所有使用optional或者repeated,儘可能不實用required。若是使用了required,須要全網統一升級,若是使用optional或者repeated能夠平滑升級。

 

⑤.默認值。當在傳遞數據時,對於required數據類型,若是用戶沒有設置值,則使用默認值傳遞到對端。當接受數據是,對於optional字段,若是沒有接收到optional字段,則設置爲默認值。

 

關於import

protobuf 接口文件能夠像C語言的h文件一個,分離爲多個,在須要的時候經過 import導入須要對文件。其行爲和C語言的#include或者java的import的行爲大體相同。

關於package

避免名稱衝突,能夠給每一個文件指定一個package名稱,對於java解析爲java中的包。對於C++則解析爲名稱空間。

 

關於message

支持嵌套消息,消息能夠包含另外一個消息做爲其字段。也能夠在消息內定義一個新的消息。

關於enum

枚舉的定義和C++相同,可是有一些限制。

枚舉值必須大於等於0的整數。

使用分號(;)分隔枚舉變量而不是C++語言中的逗號(,)

eg.

enum VoipProtocol 

{

    H323 = 1;

    SIP  = 2;

    MGCP = 3;

    H248 = 4;

}

4.  Protobuf的PHP實例

如下,爲了深入理解protobuf。咱們使用php示例:

php protobuf 下載地址http://code.google.com/p/pb4php/downloads/list

C# protobuf 下載地址http://code.google.com/p/protobuf/downloads/list
protobuf語言使用 http://www.cnblogs.com/dkblog/archive/2012/03/27/2419010.html

php使用protobuf,而後再測試通信。

下載的example的pb_proto_test_new.php是由問題的。

一、 下載:php protobuf 

下載地址http://code.google.com/p/pb4php/downloads/list

http://pb4php.googlecode.com/files/protocolbuf_025.zip

wwwroot目錄下


二、先寫一個proto文件

咱們使用庫裏面提供的proto文件:test_new.proto。這個文件是在example。咱們把它移到新建的文件mytest目錄下。

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


  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }


  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
  // a simple comment
  repeated PhoneNumber phone = 4;
  optional string surname = 5;
}


message AddressBook {
  repeated Person person = 1;
}


message Test {
  repeated string person = 2;
}

三、生成pb_proto_test_new.php文件

其實該文件已經存在example目錄下啦。

但有原始生成的這個文件有問題。根本沒有這個常量:var $wired_type = PBMessage::WIRED_STRING;


創建一個create_test_new.php文件存放編譯命令:

<?php
require_once('../parser/pb_parser.php');
$parser = new PBParser();
$parser->parse('./test_new.proto');
echo 'ok;

結果在mytest目錄下生成一個文件:pb_proto_test_new.php


到此,假如這個數據協議是在客戶端。那麼咱們客戶端也使用php代碼:咱們直接使用代碼庫example裏面的示例:


四、運行實例:

即運行test_new.php:

<?php
// first include pb_message
require_once('../message/pb_message.php');

// include the generated file
require_once('./pb_proto_test_new.php');

// generate message with the new definition with surname
// now just test the classes
$book = new AddressBook();
$person = $book->add_person();
$person->set_name('Nikolai');
$person = $book->add_person();
$person->set_name('Kordulla');
$person->set_surname('MySurname');


$phone_number = $person->add_phone();
$phone_number->set_number('0711');
$phone_number->set_type(Person_PhoneType::WORK);


$phone_number = $person->add_phone();
$phone_number->set_number('0171');
$phone_number->set_type(Person_PhoneType::MOBILE);


$phone_number = $person->add_phone();
$phone_number->set_number('030');


// serialize
$string = $book->SerializeToString();


// write it to disk
file_put_contents('test.pb', $string);

?>

test.pb是生成的二進制文件 基本結構一個字節類型+ 字節長度


五、服務器讀取協議內容.

假設test.pb文件是通過網絡傳輸到服務器上的(這裏都是在本地)。

而後服務器端也能夠根據這個協議,生成對應類。例如example下面的test.proto:

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


  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }


  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
  // a simple comment
  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}


運行test.php:

<?php
// EXECUTE test_new.php first 

// first include pb_message
require_once('../message/pb_message.php');


// now read it with the old file
// include the generated file
require_once('./pb_proto_test.php');



$string = file_get_contents('./test.pb');


// Just read it
$book = new AddressBook();
$book->parseFromString($string);


var_dump($book->person_size());
$person = $book->person(0);
var_dump($person->name());
$person = $book->person(1);
var_dump($person->name());
var_dump($person->phone(0)->number());
var_dump($person->phone(0)->type());
var_dump($person->phone(1)->number());
var_dump($person->phone(1)->type());
var_dump($person->phone(2)->number());
var_dump($person->phone(2)->type());
?>

讀取出客戶端相應的內容。


 

5.  Protobuf與Thrift

數據類型

thrift thrift thrift thrift
double double float     byte   i16
int32 i32 int64 i64 uint32   uint64  
sint32   sint64   fixed32   fixed64  
sfixed32   sfixed64   bool bool string string
bytes binary message struct enum enum service service

綜合對比

  protobuf thrift
功能特性 主要是一種序列化機制 提供了全套RPC解決方案,包括序列化機制、傳輸層、併發處理框架等
支持語言 C++/Java/Python C++, Java, Python, Ruby, Perl, PHP, C#, Erlang, Haskell
易用性 語法相似,使用方式等相似
生成代碼的質量 可讀性都還過得去,執行效率另測
升級時版本兼容性 均支持向後兼容和向前兼容
學習成本 功能單一,容易學習 功能豐富、學習成本高
文檔&社區 官方文檔較爲豐富,google搜索protocol buffer有2000W+結果,google group被牆不能訪問 官方文檔較少,沒有API文檔,google搜索apache thrift僅40W結果,郵件列表不怎麼活躍

性能對比
因爲thrift功能較protobuf豐富,所以單從序列化機制上進行性能比較,按照序列化後字節數、序列化時間、反序列化時間三個指標進行,對thrift的二進制、壓縮、protobuf三種格式進行對比。

測試方法:取了15000+條樣本數據,分別寫了三個指標的測試程序,在我本身的電腦上執行,其中時間測試循環1000次,總的序列化/反序列化次數1500W+。

平均字節數

thrift二進制 535
thrift壓縮 473
protobuf 477

序列化(1500W次)時間(ms)

thrift二進制 306034
thrift壓縮 304256
protobuf 177652

反序列化(1500W次)時間(ms)

thrift二進制 287972
thrift壓縮 315991
protobuf 157192

thrift的時間測試可能不是很準,因爲thrift產生代碼的複雜性,編寫的測試代碼爲了適應其接口,在調用堆棧上可能有一些額外開銷。

相關文章
相關標籤/搜索