最近在选用NOSQL数据库的时候最终选择了mongodb, 感觉其各方面都很优秀, 于是为服务器增加了一组mongodb的接口, 以方便LUA逻辑层使用.
驱动方面选用了官方指定的C DRIVER, 大家有兴趣的可以直接去mongodb的官网上查找, 不过查看了其mongo_find接口发现为同步调用, 这在服务器并发应用方面会受到限制, 通常服务器为了提高并发处理效率会使用异步接口. 开始的时候比较偷懒, 在其官网上留言想让其driver开发人员帮忙添加异步调用接口, 这事情也就先放下了... 2天过后, 等我想要接着制作的时候, 再次查看了官网动态... 发现死气沉沉的毫无反应... 我个人还算是比较勤快, 好在driver代码实在好读, 这点让我很佩服, 仔细的分析了下原尾, 决定自己为其添加一组异步调用接口 :)
ok, let's do it :)
由于鄙人资历经验尚浅... 如有不妥之处, 欢迎各位及时指正.
我们知道LUA中提供coroutine, 这极大的方便了我们开发异步调用, 首先, 我们需要明确异步查询流程: LUA脚本层发起查询请求, 调用引擎层接口发送查询请求, 返回LUA脚本层并挂起当前coroutine, --- (引擎处理其他事件) ---, 引擎收到mongodb返回查询结果, 组织封装结果并通知LUA脚本层唤醒查询请求coroutine, coroutine继续执行得到返回结果并处理.
由于有coroutine的帮助, 使得我们在LUA层面上的调用和正常的顺序编程没有区别, 而异步的回调处理过程完全被屏蔽在逻辑层之下, 大大的简化了我们的逻辑层编程复杂度, 而且提高了引擎并发处理能力.
好了, 接下来我们来看看如何实现这样的功能. 根据上面的思路, 我们需要首先解决一个问题, 如何正确的恢复一个被挂起的coroutine, 这里如果我们使用多个线程等待同一条连接上的请求响应时, 如果在未加锁保护的情况下会出现这样一种情况: 比如2个线程通过同一条连接请求A, B发送至数据库, 但接受情况为B, A. 因为加锁实属下策, 所以, 我们应当让数据库帮助我们记录一些信息以便在返回的结果中我们可以正确的唤醒相应的coroutine.
mongodb的消息头中包含了这样的字段, 我们可以暂且为其命名为query_id, 当我们在发送时填充该字段后, 在接受到的返回消息头中会原封不动的得到该query_id, 这样我们就可以简单的使用自增id作为query_id进行填充即可.
下面我们要用将原driver中的find接口进行拆分, 原有接口的逻辑为: 创建消息头, 填充query, 发送请求, 等待接受结果, 接收到结果并返回. 我们将其拆分为3个接口:
1.创建并发送请求
2.接受返回结果消息头
3.接受返回结果数据并做相应的回调处理.
这样, 我们可以非常方便的将其放入如libevent的事件驱动内进行处理.
因为第二个和第三个接口都在接收返回结果时调用, 此过程为连续过程, 所以不用担心这里面会有乱序的情况.
下面放上代码:(此代码为在原作者接口基础上进行拆分和稍作修改)
这里面需要注意的地方就是在处理回调的过程中需要妥善保存好发起请求时传入的参数"ns", 此为database.collection, 简单的做法就是使用发送请求时生成的query_id作为key, 将ns copy出来存入hash结构中, 在响应回调时取出并正确的处理结果 :)
经过简单测试, 还是比较满意这次的改动, 不过细节方面还有待改善, 不过这些代码仅作为初步使用, 昨天下午的时候我也将这些改动的patch发给了原driver制作组, 他们的反馈意见是会仔细考虑这个意见, 不过他们还要处理优先级更高的任务 :D , 所以, 如果想使用最终官方的接口, 还是要等driver制作组的更新了. 本人的改动仅作为参考和测试.
大家有什么问题相互探讨可以随时留言或email给我(hyzwowtools@163.com)