Thrift 的技巧 - 消息和存储。
希望这能帮助那些正在尝试解决和我遇到过的相同问题的人。
- 下载 flash_example.zip - 1,020 KB
- 下载 ffthrif_cpp_example-noexe.zip - 4.3 MB
- 下载 ffthrif_cpp_example.zip - 6 MB
引言
我已经成功地使用 Thrift (http://thrift.apache.org/) 设计和开发了游戏服务器。 在这里写一些技巧来分享我的经验。 希望这能帮助那些正在尝试解决和我遇到过的相同问题的人。
背景
Thrift 主要用于可扩展的跨语言服务开发(正如其网站上所写的那样)。 有人可能会提到 Google Protocol Buffers。 我在一些项目中使用过 Protocol Buffers。 但是,我更喜欢 Thrift,原因有两个:
- Thrift 支持数组和 Map。 这太好了。
- Thrift 框架生成的代码更具可读性,就像我自己写的一样。
在开发 MMO 服务器时,我使用消息与客户端通信,客户端可能由不同的语言实现,例如 as3。 所以 Thrift 非常适合用于交换消息。 但是还有另一种使用 Thrift 的情况。 众所周知,游戏服务器的部分逻辑经常被修改。 随着项目的进展,更多的属性将被添加到玩家对象或子结构中。 因此,我们将向数据库添加更多字段或行以存储额外数据。 考虑到版本问题,这非常令人恼火。 我在数据库表中创建了一个 blob 字段,用于存储 Thrift 对象。 凭借版本支持的优势,很容易向玩家添加更多属性。
Flash AS3 封装
Thrift 最初是为 RPC 设计的。 如果您在 Google 上搜索,很容易找到它的 RPC 工作原理。 我不会为这部分写更多。 但是,很少有文章介绍如何在实时系统中使用 Thrift 来传递消息。 Thrift 支持 as3,但仅用于 RPC。 因此,我添加了一些文件来支持消息通信。 这也是我喜欢 Thrift 的另一个原因,因为代码非常易读,因此很容易扩展 Thrift。 虽然我不是专业的 as3 工程师(我是一名专业的 C++ 工程师),但我很容易实现它。 我实现了两个函数来封装序列化和反序列化。
public class FFUtil {
public static function EncodeMsg(msg:TBase, ba:ByteArray):void
{
var tran:FFMemoryTransport = new FFMemoryTransport(ba);
var proto:TBinaryProtocol = new TBinaryProtocol(tran);
msg.write(proto);
}
public static function DecodeMsg(msg:TBase, ba:ByteArray):Boolean
{
var tran:FFMemoryTransport = new FFMemoryTransport(ba);
var proto:TBinaryProtocol = new TBinaryProtocol(tran);
msg.read(proto);
return true;
}
}
我已经上传了 flash_example.zip 文件,其中包含我的扩展 as3 库文件和一个 exampl.as 文件。 实际上它是一个使用 Thrift 的聊天教程应用程序。
C++ 封装
Thrift C++ 实现代码中存在一些令人不满意的地方,它们依赖于 Boost 库。 一般来说,这不是一个大问题,因为大多数 C++ 项目都在使用 Boost。 但是,一些只想轻松集成 Thrift 的人会觉得这很烦人。 在一些带有 C++ 的移动应用程序项目中,他们也不想依赖 Boost。 实际上,我没有找到足够好的理由必须使用 Boost 来实现 Thrift 的 C++ 版本。 Boost 在 Thrift 中的大多数用途都是用于断言测试和智能指针。 感谢可读的代码,我发现很容易消除对 Boost 的依赖。 实际上,我仍然建议您使用官方版本。 但是,如果您只是想尽快尝试 Thrift,您可以尝试这个没有 Boost 的版本。 顺便说一下,这个没有 Boost 的 C++ 版本仅支持二进制编码。
int main(int argc, char **argv)
{
shared::SharedStruct msg, dest;
msg.value = "nihao";
std::string data;
ffthrift_t::EncodeToString(msg, data);
printf("data size=%u\n", data.size());
ffthrift_t::DecodeFromString(dest, data);
printf("dest value=%s\n", dest.value.c_str());
return 0;
}
我已经上传了 ffthrif_cpp_example.zip 文件,其中包含我的扩展 C++ 文件和一个示例文件。
关于存储
我使用 MySQL。 在这里,我将游戏结构的模型作为一个例子。 为了存储玩家的数据,我设计了一个名为 player_info 的表,用于玩家的基本属性,例如等级、金币、经验……
Extra_Data 字段是特殊且重要的。 它是一个 blob 类型,用于存储 Thrift。 例如,我设计了这样一个 Thrif 文件
namespace cpp ff
namespace as3 ff
namespace py ff
struct extra_data_t {
1: list<i32> val1
2: string val2
3: map<i32, string> val3
}
thrift-0.9.0.exe -gen cpp msg_def.thrift
我将存储 extra_data_t
到 Extra_Data 字段。 有以下特点:
- 由于对版本的支持,我们可以向
extra_data_t
添加更多字段,但不需要修改 MySQL 表结构。 我们有数百个游戏服务器,因此我们总是先更新一些服务器,如果一切正常,我们会将所有服务器更新到最新版本。 Thrift 和 blob 使我们能够足够灵活地处理这些情况。 我们不需要频繁地修改数据库,并且数据库操作的部分是稳定的。 - Thrift 支持 list 和 map,这对于我们来说很方便,并且它的代码非常易读,我们可以将
extra_data_t
用作player_t
类的基本结构。
但是我们必须正确地使用 Thrift。 如果您选择将 Thrift 对象存储到数据库 blob 中,您将放弃通过 SQL 查询该字段。 所以我的建议是:
- 将那些需要通过 SQL 查询的字段存储为数据库的基本类型。 例如等级、经验、金币、分数……
- 将那些不需要通过 SQL 查询的字段存储在 Thrift 对象中。 虽然数据库 blob 中的 Thrift 对象无法通过 SQL 查询,但这并不意味着无法查询它。 感谢 Thrift 对多种语言的支持,Thrift 对象可以通过脚本查询。 我们开发了一些带有 PHP 的网页来显示来自数据库的数据。
摘要
- 实际上,在这里,Thrift 是一个序列化和反序列化框架,而不是 RPC 框架。
- RPC 机制不适合我。 我只是使用 Thrift 来生成支持版本的消息。
- 通过支持版本,Thrift 可以用于数据库存储数据。 通过将 Thrift 对象存储在数据库 blob 中,我们将变得更加灵活。