protobuf是google的一個開源序列化框架,基於二進制數據交換格式,兼顧了效率和靈活性。詳見http://code.google.com/p/protobuf/。html
本文假定讀者對protobuf已經有了初步接觸,故略過一些基本和細節的描述,着重於介紹protobuf在筆者項目中的應用思路。因項目主要編程語言 是c++,因此本文的示例着眼於如何在c++中藉助protobuf簡化一些通用模塊的處理。對於其它語言(java,python等),若是語言自己沒 有更好用的特性或者更方便的庫的話,也可採起相似作法。前端
雖然protobuf最普遍的應用是網絡數據交換,但在使用過程當中發現其框架自己的設計具備極靈活的擴展性,對於服務器來講,徹底能夠做爲通用的數據解析層。一言以蔽之,即能夠將各類形式的外部數據以很是易用的方式存取,經過最天然的本地語言對象進行操做(在本文中是c++)。藉助protobuf框架的如下特性,構建protobuf生態圈將會是一個輕鬆愉快的過程:java
protobuf提供了自定義代碼生成器的支持——「代碼生成器」遠沒有它聽起來那麼複雜,相反,得益於protobuf合理的架構設計,筆者項目中的各種生成器通常只有幾百行代碼。python
protobuf的代碼生成器能夠跟靜態語言的編譯器進行類比,編譯器:mysql
源碼 ---> (編譯器前端) ---> 中間代碼 ---> (編譯器後端) ---> 機器碼c++
而protobuf的代碼生成過程以下:sql
proto文件 ---> (解析器) ---> 本地語言對象模型 ---> (代碼生成器) ---> 本地語言源碼數據庫
咱們要作的僅僅是自定義代碼生成後端,只需關心proto文件中定義的對象模型便可,複雜的proto文件解析由protoc內置的解析器完成。編程
protobuf提供了反射機制(包括c++的版本),以實現對運行期對象進行內省。相關示例代碼見http://code.google.com/apis/protocolbuffers/docs/reference/cpp/google.protobuf.message.html。後端
藉助反射,能夠完成一些在傳統c++設計領域中難以完成的事情,好比對一個對象的全部屬性進行迭代,好比根據屬性的名字進行存取等等。其實現也至關高效,例如以下代碼:
Message* message = ...; const Reflection* reflection = ...; const FieldDescriptor* field = ...; reflection->SetInt32(message, field);
最後一行對於message相應屬性的定位是直接經過地址偏移來實現的(有點hack的意味),其偏移值經過代碼生成器生成並保存在FieldDescriptor中。
因爲語言特性的緣由,c++中對數據庫的操做一直是個比較繁瑣的過程。以mysql爲例,官方的c api遠不適合不加封裝直接使用,mysql++庫的Specialized SQL Structures尚可,但也還不夠便利,好比沒有has語義使得只update一個結構的其中幾個字段的寫法比較冗長,好比表的字段有增刪須要手動維護c++中相應的結構等等。
利用protobuf,徹底能夠實現更易用而強大的數據庫粘合層。
如下是筆者項目中數據庫粘合層的用法示例(爲了便於說明,作了些簡化,沒有演示索引、字符集、存儲引擎等功能)。
首先,定義proto文件。
test_data.proto:
message TestData { required int32 id = 1; optional string name = 2; }
這樣,自定義的代碼生成器會生成如下sql(在筆者的項目中,這一步是自動完成的,每當有須要導出sql的proto文件,則編譯時自動生成相應sql並執行,相應的DROP語句是版本降級時用的):
CREATE TABLE TESTDATA( id INT NOT NULL, name VARCHAR); DROP TABLE TESTDATA;
在c++中,就能夠用如下方式操做:
Query query = database->Query(); TestData data; // proto文件中定義的對象 data.set_id(1); data.set_name("Snake"); if (!query.Insert(data)) { // INSERT INTO TESTDATA (id, name) VALUES(1, "Snake"); .... } data.set_name("Raiden"); TestData where; where.set_id(1); if (!query.Update(data, where)) { // 經過同類型對象來描述where子句, 至關於where id=1 ... } std::vector<TestData> messages; query.Select(messages, // 把結果集儲存到messages中,接受std順序容器, 也接受單個message Columns("id")("name"), // 指定column, SELECT * 可用AllColumns() Where("id") == 1 && Where("name") == "Raiden", // 另外一種形式的WHERE子句 OrderBy("id").Desc(), // ORDER BY ID DESC Limit(10)); // LIMIT 10, 僅做爲示例 for (size_i = 0; i < messages.size(); ++i) { std::cout << messages[i].DebugString(); } query.Delete<TestData>(); // DELETE FROM TESTDATA, 一樣支持兩種形式的WHERE
以上只是基本用法示例。在筆者項目中實現的一些擴展功能還有(主要是利用protobuf的option機制實現):
一、導出控制。能夠靈活設置單個proto文件、單個消息、單個字段是否須要導出。
二、版本號控制。能夠指明某個字段的版本號,自動化生成相應腳本,方便運營時升級/降級。
四、對複雜結構的支持。原生數據類型基本上均可以跟sql中的數據類型一一對應,對於repeated字段和子message,採用blob類型存儲其序列化後的數據。
五、數據庫相應選項聲明。好比設置表索引、所用引擎、字符集、字段長度、auto_increment等等。
六、增量update。能夠自動比較message與上次update的差別,只update有改動的字段。
考慮以下案例:給原有的數據庫表增長一個字段,讀寫,發送給其它網絡主機進行處理。採用最原始的純手工方式須要經歷如下步驟:
一、手動寫alter table並執行;
二、給c++中相應結構增長字段;
三、修改insert,select,update等等涉及該字段的sql;
四、修改相關網絡同步的消息代碼,增長此字段的同步;
很快,涉及數據庫的改動成爲了bug滋生的溫牀。
再看看前述方案,好比須要給TestData增長一新字段new_column,只須要作這麼一件事,很好的體現了SPOT原則:
message TestData { required int32 id = 1; optional string name = 2; optional float new_column = 3 [(sql.column_version) = "1.0.1"]; }
new_column 爲新增字段,其後跟隨的選項描述了其版本號。不用再作其它操做——alter table的sql語句被自動生成並執行,update、insert、select等調用自動兼容(內部實現是經過反射機制來拼接sql語句的),結構 天生爲網絡數據交換而設計,因此不用改動網絡同步相關代碼,也不用操心版本不匹配的問題。Perfect!
...待續,下篇將介紹protobuf與lua結合、用protobuf進行配置文件解析等方案。