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