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,是一个半成品,一直在忙其他的,没空完善。
来源: http://www.cnblogs.com/coding-my-life/p/7296323.html