12306票池架构探讨(三)
在前面的文章里,12306票池架构探讨(一)和12306票池架构探讨(二)里大概说了下票池实现的思路和选用的数据结构(数据结构上还有些争议),主要的思想就是将整个票池放在内存里 – 整个数据库都在内存里。
关于票池的需求,请参看我的另一篇帖子:http://12306ng.org/thread-1682-1-1.html。
架构设计
整个票池的架构如下图所示:
系统其他模块(或者就是服务网关)可以通过RMTP(Reliable Multicast Transport Protocol)或者其它协议向票池发送消息,比如查询车次、占票、买票等消息;票池模块异步处理消息,将结果以某种(例如广播)方式返回给模块(或者就是服务网关)。
票池服务器集群分三种:
1. 票池修改服务器,负责维护票池里的具体更新,比如设置某张车票已经被占用或售出等等。这些服务器只处理更新消息,以便能及时处理售票请求,现在设想的修改流程(以占票为例,购票的流程一样)是:
a) 其他模块发送占票消息,票池修改服务器异步处理占票消息。
b) 票池修改服务器定期(比如每几毫秒)向查询服务器和备份服务器广播这段时间内票池的更新(或者改成固定数量的车票更新 - 以保证数据包的大小一致)。
c) 广播到查询服务器的消息是发出去后就不管了。
d) 广播到备份服务器的消息将要求备份服务器返回确认码,收集到确认码之后才可以认为占票成功。备份确认码的方式可以是要求收集到所有备份服务器的确认码才认为成功,和收集到大部分备份服务器(例如一半以上)的确认码才认为成功。
e) 收集到足够的备份确认码之后,票池修改服务器将被占的票异步返回给其他模块(也可以考虑广播模式,只要求其它模块返回一个确认码就认为成功,否则重新广播)。
2. 查询服务器,处理所有的查询车次、查票等消息。
3. 备份服务器,当某个票池修改服务器挂掉的时候,自动竞选成票池修改服务器。
消息机制
票池内部通过消息队列通信,消息队列的实现机制选用的是disruptor,选用它主要是出于以下几个考虑:
1. 采用Java实现,考虑到开源函数库的丰富性、编程的简易性(相对于C++来说),我们打算用Java实现票池组件。
2. 无锁车轮队列(ring buffer),无锁编程是支持高并发的先决条件,无锁车轮队列的实现机制,允许多个读写线程同时访问队列而不相互污染,它的实现机制在文中会讲。
3. CPU缓存友好的实现方式,它采取字节填充的方式规避了伪共享的问题。
下图有点不准确,在票池集群里,每个票池服务器都有一个自己的车轮队列用以处理消息。
车轮队列
详情请参考:http://blog.codeaholics.org/2011/the-disruptor-lock-free-publishing/
[任务: 用中文简要介绍下]
广播方式
在广播方面,打算使用RMTP协议,实现细节可以参考:http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.83.4272&rep=rep1&type=pdf
[任务: 用中文简要介绍下]
事件驱动(Event Source)
事件驱动的设计思路类似文件版本控制系统的实现原理,变更是累加的,可以随时回滚。细节参考:http://martinfowler.com/eaaDev/EventSourcing.html。
[任务: 用中文简要介绍下]
灾难恢复处理
在灾难恢复方面,打算使用类似MemSql的方式,每个票池修改服务器(包括备份服务器)会将所接收到的事件队列先压缩然后再顺序写入磁盘中,定期设置镜像,服务器崩溃后,恢复时从上次镜像开始恢复。具体细节请参考链接例的MemSQL Durability一节:http://highscalability.com/blog/2012/8/14/memsql-architecture-the-fast-mvcc-inmem-lockfree-codegen-and.html。
[任务: 用中文简要介绍下]
另外,这个想法还没有经过论证,因为Facebook的MySQL工程师说:http://www.csdn.net/article/2012-06-28/2806979