Protobuf動態解析那些事兒


需求背景

在接收到 protobuf 數據之后,如何自動創建具體的 Protobuf Message 對象,再做反序列化。“自動”的意思主要有兩個方面:(1)當程序中新增一個 protobuf Message 類型時,這部分代碼不需要修改,不需要自己去注冊消息類型,不需要重啟進程,只需要提供protobuf文件;(2)當protobuf Message修改后,這部分代碼不需要修改,不需要自己去注冊消息類型,不需要重啟進程只需要提供修改后protobuf文件

技術介紹

Protobuf的入門可以參考Google Protocol Buffer 的在線幫助 網頁 或者IBM developerwor上的文章《Google Protocol Buffer 的使用和原理》

protobuf的動態解析在google protobuf buffer官網並沒有什么介紹。通過google出的一些參考文檔可以知道,其實,Google Protobuf 本身具有很強的反射(reflection)功能,可以根據 type name 創建具體類型的 Message 對象,我們直接利用即可,應該就可以滿足上面的需求。

實現可以參考淘寶的文章《玩轉Protocol Buffers 》,里面對protobuf的動態解析的原理做了詳細的介紹,在此我介紹一下Protobuf  class diagram。

 

大家通常關心和使用的是圖的左半部分:MessageLite、Message、Generated Message Types (Person, AddressBook) 等,而較少注意到圖的右半部分:Descriptor, DescriptorPool, MessageFactory。

上圖中,其關鍵作用的是 Descriptor class,每個具體 Message Type 對應一個 Descriptor 對象。盡管我們沒有直接調用它的函數,但是Descriptor在“根據 type name 創建具體類型的 Message 對象”中扮演了重要的角色,起了橋梁作用。上圖的紅色箭頭描述了根據 type name 創建具體 Message 對象的過程。

實現

先直接上代碼,這個代碼來自於《玩轉Protocol Buffers 》

#include <iostream>

#include <google/protobuf/descriptor.h>

#include <google/protobuf/descriptor.pb.h>

#include <google/protobuf/dynamic_message.h>

#include <google/protobuf/compiler/importer.h>

  

using namespace std;

using namespace google::protobuf;

using namespace google::protobuf::compiler;

  

int main(int argc,const char *argv[])

{

    DiskSourceTree sourceTree;

    //look up .proto file in current directory

    sourceTree.MapPath("","./");

    Importer importer(&sourceTree, NULL);

    //runtime compile foo.proto

    importer.Import("foo.proto");

  

const Descriptor *descriptor =    importer.pool()->

      FindMessageTypeByName("Pair");

    cout << descriptor->DebugString();

  

    // build a dynamic message by "Pair" proto

    DynamicMessageFactory factory;

    const Message *message = factory.GetPrototype(descriptor);

    // create a real instance of "Pair"

    Message *pair = message->New();

  

    // write the "Pair" instance by reflection

    const Reflection *reflection = pair->GetReflection();

  

    const FieldDescriptor *field = NULL;

    field = descriptor->FindFieldByName("key");

    reflection->SetString(pair, field,"my key");

    field = descriptor->FindFieldByName("value");

    reflection->SetUInt32(pair, field, 1111);

  

    cout << pair->DebugString();

    delete pair;

    return0;

}

 

那我們就來看看上面的代碼

1)把本地地址映射為虛擬地址

DiskSourceTree sourceTree;

    //look up .proto file in current directory

sourceTree.MapPath("","./");

2)構造DescriptorPool

Importer importer(&sourceTree, NULL);

    //runtime compile foo.proto

importer.Import("foo.proto");

3)獲取Descriptor

const Descriptor *descriptor = importer.pool()->FindMessageTypeByName("Pair");

4)通過Descriptor獲取Message

const Message *message = factory.GetPrototype(descriptor);

5根據類型信息使用DynamicMessage new出這個類型的一個空對象

Message *pair = message->New();

6通過Messagereflection操作message的各個字段

  const Reflection *reflection = pair->GetReflection(); 

    const FieldDescriptor *field = NULL;

    field = descriptor->FindFieldByName("key");

    reflection->SetString(pair, field,"my key");

    field = descriptor->FindFieldByName("value");

reflection->SetUInt32(pair, field, 1111);

直接copy上面代碼看起來我們上面的需求就滿足了,只是唯一的缺點就是每次來個包加載一次配置文件,當時覺得性能應該和讀取磁盤的性能差不多,但是經過測試性能極差,一個進程每秒盡可以處理1000多個包,經過分析性能瓶頸不在磁盤,而在頻繁調用malloc和free上。

看來我們得重新考慮實現,初步的實現想法:只有protobuf描述文件更新時再重新加載,沒有更新來包只需要使用加載好的解析就可以。這個方案看起來挺好的,性能應該不錯,經過測試,性能確實可以,每秒可以處理3萬左右的包,但是實現中遇到了困難。要更新原來的Message,必須更新Importer和Factory,那么要更新這些東西,就涉及到了資源的釋放。經過研究這些資源的釋放順序特別重要,下面就介紹一下protobuf相關資源釋放策略。

動態的Message是我們用DynamicMessageFactory構造出來的,因此銷毀Message必須用同一個DynamicMessageFactory。 動態更新.proto文件時,我們銷毀老的並使用新的DynamicMessageFactory,在銷毀DynamicMessageFactory之前,必須先刪除所有經過它構造的Message。

  原理:DynamicMessageFactory里面包含DynamicMessage的共享信息,析構DynamicMessage時需要用到。生存期必須保持Descriptor>DynamicMessageFactory>DynamicMessage。 

釋放順序必須是:釋放所有DynamicMessage,釋放DynamicMessageFactory,釋放Importer。

總結

資源釋放前,必須要了解資源的構造原理,通過構造原理反推釋放順序,這樣就少走彎路、甚至不走。

 

 

參考文獻

Google Protocol Buffer 的在線幫助 網頁 

一種自動反射消息類型的 Google Protobuf 網絡傳輸方案

《玩轉Protocol Buffers 》

《Google Protocol Buffer 的使用和原理》

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM