Protocol Buffers是Google跨語言、跨平台的通用序列化庫。FlatBuffers同樣出自Google,而且也跨語言跨平台,但更強調效率,專門為游戲開發打造。在游戲界混了幾年,各種各樣的序列化協議都見過,MUD的字符串、Json、二進制、Protocol Buffers,各有各的優缺點。
Protocol Buffers采用的是單個字段壓縮到數組的方式。例如:
message CPing
{
int32 x = 1;
int32 y = 2;
int32 z = 3;
int32 way = 4;
}
則字段x的索引為1,y的索引為2,依此類推,最終經過Protocol Buffers把索引、數據都壓縮后,在內存中大概是這樣排列的:

FlatBuffers則采用內存映射的方式,例如:
table CPing
{
x:int;
y:string;
}
參考C結構體在內存中的結構模型,像int這種內存不變的,稱為POD類型,無法預先知道長度的(比如字符串),稱為指針類型。FlatBuffers直接把內存中結構體類型直接搬到了序列化內存中。header總是在最前端,記錄了各個成員的位置。各個成員的位置如果是POD類型,則記錄數據,如果是指針類型,則記錄數據位置。然后通過嚴格的內存對齊參數,用編譯器實現跨語言、跨平台。大概是這樣:

Protocol Buffers和FlatBuffers具體的序列化、反序列化還有很多細節,比如壓縮算法、內存如何對齊,這里難以詳細說明,有興趣可以自己去查資料。
我自己業余實現了一個服務器框架,以C++為底層,Lua作為上層邏輯腳本。為了提高開發效率,所有消息到達腳本時都會自動序列化為Lua的table,不需要開發人員去解析數據包。例如:
message CPing
{
int32 x = 1;
int32 y = 2;
int32 z = 3;
int32 way = 4;
}
到達腳本時,就會是一個table,如:
{ x = 999, y = 123, z = 777, way = 0, }
所使用的庫為:
Protocol Buffers:https://github.com/cloudwu/pbc
Flatbuffers:https://github.com/changnet/lua_flatbuffers
現在為了測試打包、解包效率,設計了這么一個流程:

玩家actor的數據包先經過網關gateway,再由網關轉發給游戲世界world。然后world會返回數據給gateway,再轉發給actor。開啟4個進程,每個進程登錄2500個玩家,每個玩家1000個數據包,每秒發送8個,所以最快是1000 / 8 = 125秒。系統為ubuntu 14.04,Docker版本 17.03.1-ce, build c6d412e ,程序編譯參數為-g0 -O2,所有進程運行在同一Docker中,機器配置為hp probook 4446(cpu為A8-4500m):
CPU MHz: 1400.000 BogoMIPS: 3792.91 Virtualization: AMD-V L1d cache: 16K L1i cache: 64K L2 cache: 2048K NUMA node0 CPU(s): 0-3
數據包為:
// 玩家發包
message CPing
{
int32 x = 1;
int32 y = 2;
int32 z = 3;
int32 way = 4;
}
// 服務器回包
message SPing
{
int32 time = 1;
}
Protocol Buffers的成績為:


FlatBuffers的成績為:


可以看到,兩個庫的效率相差無幾(其實因為發的數據包太簡單,完全看不出來),但是Protocol Buffer的world進程使用的cpu較高,而gateway較低,說明打包消耗了更多的cpu和時間,但是轉發時流量小,IO更低。而FlatBuffers則反過來了。
上面測試的例子比較簡單,都沒有數組和字符串,在發送的數據加上數組和字符串:
message CPing
{
int32 x = 1;
int32 y = 2;
int32 z = 3;
int32 way = 4;
repeated int32 target = 5;
string say = 6;
}
發送的時候,數組固定為:{ 1,2,3,4,5,6,7,8,9 }而字符串固定為:"android ping test android ping test android ping test android ping test android ping test"。同時玩家的數量減為5000,進程改為個,依然是每個進程2500個玩家。
Protocol Buffers成績:


FlatBuffers成績為:


可以看到,加上數組和字符串后,FlatBuffers消耗的cpu資源遠小於ProtocolBuffers,但是效率上的差距因為測試的方法不當則看不出來。
看到這里,大家可能很不理解我測試的方式,cpu基本沒有跑滿過。首先,我沒辦法讓cpu剛好跑滿,因為玩家那個進程是采用定時發包的方式來模擬玩家操作而不是echo方式(收到回包后再發包),所以多個玩家進程懟一個服務器進程,只要gateway和world進程有一個吃不消,就會造成數據包堆積。其次,我做這個測試是為了證明這兩個庫集成到我的框架中后能否達到我期望的效率。再着,這個測試中Lua的gc消耗可能是影響最大的一個因素。所以,這里只是一個參考,如果你要單純測試這兩個庫的效率,可以直接測試那兩個庫(網上已經有不少結果了)。
FlatBuffers的效率要高一些,而Protocol Buffers的流量要小一些,而且Protocol Buffers的使用更加廣泛、成熟。項目中使用哪個,就要看個人取舍。其實,我最早寫了一個二進制序列化的庫,使用Json作為schema文件,也能達到自動打包、解包的效果。但是不能實現版本向后兼容,也不能實現字段冗余,不過效率比這兩個都要高。后來我嫌棄自己代碼寫得爛,就從框架分離出去了,等有時間再整理成一個獨立的庫,放在https://github.com/changnet/lua_stream。
測試框架代碼在https://github.com/changnet/MServer,是一個半成品,一直在忙其他的,沒空完善。
