MongoDB 的 Lua Driver
最近听从同事建议想尝试一下 MongoDB 。
前年,图灵的同学送过我一本《MongoDB权威指南》 ,当时我花了两个晚上看完。我所有的认知就是这本书了。我们最近的合作项目 狂刃 也是用的 MongoDB ,最近封测阶段,关于数据库部分也出过许多问题。蜗牛同学在帮助成都的同学做调优,做了不少工作。总是能在办公室里听到关于 MongoDB 的话题。
我打算为 skynet 做一个 MongoDB 的 Driver 。
Skynet 默认是用 lua 做开发语言的。那么为什么不直接用 luamongo 呢?
因为 skynet 需要一个异步库,不希望一个 service 在做数据库操作的时候被阻塞住。那么,我们就不可能直接把 luamongo 作为库的形式提供给 lua 使用。
一个简单的方法是 skynet 目前对 redis 做的封装那样(当然,skynet 中的 redis 封装也是非阻塞的),提供一个独立的 service 去访问数据库,然后其它服务器向它发送异步请求。如果我直接使用 luamongo 就会出现一个问题:
我需要先把请求从 lua table 序列化,发送给和 mongoDB 交互的位置,反序列化后再把 lua table 打包成 bson 。获得 MongoDB 的反馈后,又需要逆向这个流程。这是非常低效的事情。如果我们可以直接让请求方生成 bson 对象,这样就可以直接把 bson 对象的指针发过到 交互模块就够了( skynet 是单进程模型,可以在服务内直接交换 C 指针)。这就需要我定制一套 lua moogodb 的 driver 了。
我是从昨天正式开始进行这个工作的。原以为,有 MongoDB 的 C Driver 打底,应该也就是一天的工作量。结果却不是这样。
首先我研究了 bson 这种数据格式。以前也听过它,以为就是 json 的 binary 形式而已,是一种简单通用的结构化数据交换格式。研究后的结论是,bson 并非一种通用数据格式,而是为 MongoDB 专门设计的,虽然它希望成为通用格式,但协议设计中却充满了 MongoDB 特有的东西。这让我小小不爽。
MongoDB 的 C Driver 是官方维护的。一开始我是挺信任它的代码的。但做下来非常失望。首先文档跟不上更新,当然这也不太所谓,反正我不怎么读文档,都是直接看 .h 文件做的。我试图在这个 C Driver 提供的 bson 库的基础上封装 lua 接口。做到快结束的时候发现它没有支持 bson 标准中的 minkey 和 maxkey 两个东西。虽然加上是举手之劳,但我一直没有找到方便的提交问题的渠道。从 github 上找到维护人的 email ,写了封邮件去问,还没有得到反馈。不过最终我还是把这个东西做好了。
接下来做 mongo 其它部分的封装。发现了更多的问题。由于文档比较老,我在用 api 的时候都是直接读 .c 里的实现的。
比如 mongo_set_write_concern
可以给连接设置一个默认的 write concern 对象,但它的实现里只是保存了 mongo_write_concern
指针,而无视了这个对象的生命期。
如果说这个问题不太严重的话,这个接口可能更能说明问题:
MONGO_EXPORT void mongo_write_concern_set_mode( mongo_write_concern *write_concern, const char* mode ){ write_concern->mode = mode; }
mode 这个字符串并没有复制。一般 C 程序用的时候可能不太能暴露问题,但如果做 lua 的绑定的话, mode 就一定是从 lua state 里传过来的 lua string 了。一旦 string 被 gc 回收,就会导致悬空指针的问题。
我现在没用一个 api 就很不放心的读一遍实现。照这个做法,真不如放弃掉封装 C Driver 的想法,从协议层直接去实现呢。
结论是 MongoDB 的 C Driver 的代码质量很糟糕。
下面我谈谈关于 Lua 封装库的一些问题:
对于 bson 中的几个复杂对象,例如 objectid timestamp 等类型,luamongo 里是做了一个 full userdata ,并且附加了 metatable 。我觉得这种方式并不好。会导致每次对 mongoDB 的消息处理都生成许多的 userdata 。为了回避这个问题,我想了两一个方法。
bson 的字符串类型要求是 UTF-8 编码的,所以不是 UTF-8 的串都需要用 binary 格式编码。我们可以利用这一点,构造一个非 UTF-8 的 string 来表示别的类型的对象。
我的方法是:所有 00 XX 开头的字符串都是扩展类型。这里 XX 是扩展类型的 type 。这样,我把 bson 需要的扩展类型,包括 binary 、timestamp 、date、minkey、maxkey、objectid 等等都编码成了 lua 的 string ,回避了 userdata 的开销。构造这些 mongo 扩展类型就是构造一个特殊的 lua string 。编码解码的成本都相当的低。
我打算重新按 bson 协议实现一遍 mongoDB 的 lua driver ,所以目前已经完成的 C Driver 的 lua 封装暂时就不开源了。
Comments
Posted by: David Xu | (9) June 13, 2013 03:36 PM
Posted by: hanson | (8) June 9, 2013 04:06 PM
Posted by: Davies | (7) June 8, 2013 03:56 PM
Posted by: Simon | (6) June 8, 2013 03:08 PM
Posted by: Cloud | (5) June 8, 2013 11:14 AM
Posted by: tiansheng li | (4) June 8, 2013 10:55 AM
Posted by: Rock | (3) June 8, 2013 09:48 AM
Posted by: Cloud | (2) June 7, 2013 10:40 PM
Posted by: ack | (1) June 7, 2013 09:34 PM