Proto Buffers in Lua
Google 的 Jeff Dean 同学说,设计分布式系统一定要有 Protocol Description Language。
Google Proto Buffers 的意义在于,定义了一个不错的 PDL 。protobuffers 的实现反而不那么重要了。
这几天我一直在倒腾 lua 下的 proto buffers 的支持。一直在思考,怎样的接口才是最适合 lua 使用的。
大多数语言下的 proto buffers 实现,都是将编码的数据块展开成本地语言的数据结构。对于 C/C++ ,这是最高效的形式。但对于动态语言,那就未必了。虽然 google 为 python 做的 proto buffers 的官方实现也是如此,但我依然想考虑一下,是否有更高效的方式来做这件事。
lua ,最高效的数据处理交换是利用语言的栈。虽然 table 也足够快,但复杂且深层次的 table 往往容易成为最终的性能瓶颈。btw, google 的 js V8 引擎,在部分场合表现的比 lua jit 更为出色,很大程度上是因为它极大的加快了 table 的查询。这几乎会成为所有解释型动态语言的性能热点。而且不断了生成临时 table ,还会给 gc 造成较大的负担。那么,能不能找到一种方式可以回避在 lua 中构造复杂数据结构,而又能方便的使用由 proto buffers 定义出来的结构化数据呢?
我花了三四天初步实现了一个 lua proto buffers 的库,全部是以 C 代码完成的。最终提供给 lua 的接口只有四个。
-
load 能加载一个 proto buffers 的元数据,对协议本身做一个描述。并生成一个 C 对象 (userdata) 返回到 lua state 中。这个 C 对象包含了一组 proto 描述信息,除了其中的字符串信息外,全部储存在一块连续的内存中。作为 lua 的 userdata ,不需要任何 gc 方法。其中引用的字符串全部记录在这个 userdata 的环境表中,在 C 对象里只记录字符串表里的序号。这个方式可以很大的提高 lua 运行时的字符串传递性能。
-
pattern 可以为某个 message 生成一个模式对象 (还是 C 对象),这个模式对象可以用来解码或编码数据。pattern 的概念是整个库的核心概念。库会依据 pattern 来编码或解码数据。pattern 本身的生成比较重量,但在实际应用中,往往是一次性的,而不需要对每个数据包处理时都重新生成 pattern 。做一些小手脚,就可以在 lua 层面做进一步的简单封装。用字符串的形式来 cache 并索引 pattern 。
-
unpack 可以对一个数据块进行解包工作。unpack 必须提供数据块以及相应的 pattern 对象。它会按 pattern 的指示把结构化数据解到栈上。这种方式回避了每次解包都生成临时的 table 。当然,如果使用者还是喜欢构造一个 table 来表达数据块中的数据结构的话,依然可以通过几行 lua 封装代码来办到。
-
pack 可以根据一个 pattern 对数据块打包。同样从栈接收数据源。不需要构造临时的 table 用于打包。
我在做这个库之前,做了一些工作来解析 Google Proto Buffers 定义的协议描述文件。如上所说,我认为,协议定义及描述是整个 Proto Buffers 的核心,而其实现以及二进制编码规则则是次要的东西。
google 提供了开源工具,把文本协议描述文件编译成 proto buffers 自身形式的结构化数据。这做了很漂亮,也方便了对协议本身的解析。不过若是抛开二进制编码,若想让整套协议自举,而整个过程和具体编码形式无关的话,更有趣的做法是自己来实现文本协议描述的解析。
好在用 lua 来做这项工作并不太难,proto buffers 定义的语言也相当简单,容易解析。我大约写了 100 来行 lua 代码来完成基本的语义分析。当然,用了强大的 lpeg 库。话说,PEG 是个相当强大的东西,强烈推荐没学过的同学们好好学习一下。用顺手的话,为自己的项目定义 DSL 那是易如反掌。
整个解析的实际运行过程非常繁重,但最终只是生成一个很小的 C 数据对象。我用了一个有趣的方案来干这件事:单独开一个 lua State 来完成协议的解析。这个独立的 lua State 总共所消耗的内存可以事先预估,不会太大(跟 proto 文件的规模以及复杂度有关)。所以我为这个独立的 State 定制了一个只分配不释放的内存管理器。这个内存管理器工作在独立的系统内存页上,方便最后一次回收。这样,每次解析一个协议文件,只需要消耗一些临时内存,而在解析完后,能够绝对干净的清除痕迹。我统计了一下一个最终大约 70K 的 C 数据对象,处理过程消耗了大约 1.5M 内存。
灵活使用多个 lua State 应该是一个良好的习惯。尤其在较大规模的需要长期运行程序中,把各个模块分配到独立的 lua State 空间中运行,可以使任务完成后,内存得到准确的回收。
这几天做的这个东西,还很初步,所以就不细写了。等成熟后再考虑开源。
ps. 在 lua 社区,设计些小的模式语言来完成数据处理,似乎是一个很流行的做法。lua 自带的字符串匹配如此;cosmo 也是个有趣的小玩意;lpeg 也提供一个 The re Module 来模拟正则表达式的语法。等等这些启发我设计出这个 proto buffers 的 pattern 。
建议继续学习:
- 分布式缓存系统 Memcached 入门 (阅读:14701)
- Zookeeper工作原理 (阅读:10353)
- GFS, HDFS, Blob File System架构对比 (阅读:9356)
- Zookeeper研究和应用 (阅读:8500)
- 分布式日志系统scribe使用手记 (阅读:8022)
- 一致性哈希算法及其在分布式系统中的应用 (阅读:7912)
- 分布式哈希和一致性哈希 (阅读:7634)
- HBase技术介绍 (阅读:6739)
- 分布式系统的事务处理 (阅读:5968)
- Memcache分布式部署方案 (阅读:5429)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:云风的 BLOG 来源: 云风的 BLOG
- 标签: lua 分布式
- 发布时间:2010-08-10 22:31:14
- [66] Oracle MTS模式下 进程地址与会话信
- [66] Go Reflect 性能
- [65] 如何拿下简短的域名
- [59] android 开发入门
- [59] 图书馆的世界纪录
- [59] IOS安全–浅谈关于IOS加固的几种方法
- [58] 【社会化设计】自我(self)部分――欢迎区
- [53] 视觉调整-设计师 vs. 逻辑
- [47] 界面设计速成
- [46] 读书笔记-壹百度:百度十年千倍的29条法则