在前面的文章里,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
