鐵道部新客票系統設計(二)
在上一篇文章中 鐵道部信客票系統設計(一) 里面,探討了關于數據庫層面的功能性需求以及非功能性的需求,在非功能性需求里面,一博主 提出了沒有考慮到峰值的情況,這一點的確漏掉了,因為我們鐵道部的特殊需求,在春運期間負載很大,平時可能一般,如果用考慮***的情況,則回存在浪費的情況,如果考慮不足,就像網絡訂票一樣,苦逼。就好比 鐵道部春運的時候,發車量大,但是如果制造大量列車,平時就空閑了,也就很虧。機器的折舊很是塊的。春運期間可以考慮緊急擴容來實現,所以從設計上可以保持這種擴展性。 擴容是一項工程,整體來說比較復雜。
上一篇博客發表后,也有博主和我探討過一些問題,也讓我了解到鐵道部目前的狀態。由于這個純粹是技術上的分析,先不去考慮一些政治因素,畢竟這個比技術復雜多了
正題開始,原來打算這一篇里面介紹數據庫表的設計,但是上一篇文章中還有很多細節問題,沒有解決,這里面繼續上一張,把數據這一層在慢慢完善
購票的業務流程
由于購票過程中是鐵道部售票系統的主要功能也是核心業務邏輯,這里先從購票的業務流程開始,討論購票業務流程中相關的數據庫設計
簡單的購票流程
終端-->查詢余票-->選擇車次-->確認座位-->選擇張數-->支付-->出票
這里面重要的是兩個環節 查詢余票 和 支付過程
我們先模擬以下正常網絡購票過程中數據庫的操作,這里面先把問題簡單化,假設用戶只買始發站到終點站的數據
- select * from 余票
- insert into 車票
- update 余票 -1
- update 車票 set status='WAIT_PAY' where id = xxx
- update 車票 set status='PAY' where id = xxx
電話訂票類似,只不過訂的票不會由于過期而取消,要么支付,要么退票
而在窗口、代售點買的票,支付方式只不過是現金,出票的時候自動支付。
其實無論從那個終端過來的請求,都會涉及到查詢余票,創建車票,支付車票 過程,考慮一中簡單的情況,就是用戶只查詢一次,就選擇了自己要確定的車次,然后購票,去支付。那么一次購票請求,會至少 一次余票查詢 一次余票更新,一次車票insert,兩次車票update,這個還是最少的情況,實際鐵道部的業務應該比這個復雜多了。由于查詢余票是購票請求的入口,所有的購票請求都會優先查詢余票庫
余票庫的設計
在***篇文章中,余票庫沒有設計成為讀寫分離,主要是考慮的用戶一定獲取的是最實時信息,讀寫分離的話,讀庫和寫庫的數據有效性上面會有差異,比如我更新了一個數據,必須馬上反映到余票上,否則用戶看到一個過期的數據,對用戶體驗很不好。這個庫的訪問量超級大,而且還會涉及到熱點數據的鎖定,一旦同一條數據(比如我這次想買Z27硬臥)同時被大量用戶請求,根據上面分析的,出票就要鎖定余票表中Z27這一條記錄,由于一次只允許一條用戶請求能夠獲得鎖,請求要必須盡快的處理,除了必要的原子操作,比如票數-1,產生購票表,其他的耗時操作就應該越少越好,盡量異步化操作,核心思想就是盡快的釋放鎖,否則請求排隊的線程越來越多,導致數據庫所有數據庫的連接資源被耗盡,系統會變的很慢。
整理以下:在設計余票庫的時候,在性能上提升,可以從下面幾個方面去考慮
1 盡量減少沒有必要的查詢,減少數據庫的資源消耗
2 鎖的粒度越小越好
3 訂票事務處理越短越好,消耗的業務邏輯處理越快越好,爭取***的異步處理。
接下來就是考慮如何通過上述思想,找出具體的設計方案
1 減少對數據庫的查詢
一般情況下,先會查詢某日余票信息,接下來就是根據查詢出來的信息,選擇車次,席位。然后張數,然后訂票成功。
首先,假設我們把余票信息緩存,應用先查詢緩存,如果有票,用戶選擇車次和席位,這樣會減少一次數據庫的查詢。
緩存有兩種方式,一種是應用局部緩存,每個渠道緩存票務數據,這里涉及到數據的更新以及各個緩存之間的同步,不及時,暫時不考慮。
另外一個種是分布式緩存,建立緩存服務器(這里面說的緩存都是指內存緩存),數據庫只需要和緩存服務器之間保持同步,但是這樣一來,如果會員想獲取***的數據,緩存服務器也需要保持很頻繁的更新,相當于要保持緩存和余票數據的同步。這個成本也是非常高。還有一個折中方案,就是緩存不緩存票數,而是緩存有票無票信息,每次用戶查詢票數的時候,先查緩存信息,看看是否有票,如果有票,就查詢數據查詢具體的票數,如果無票,就不需要進行查詢了,這樣減少了數據庫的查詢。同時緩存的更新也少了很多,只需要在票數等于0的時候,更新以下緩存數據。假設票在一分鐘之內賣掉,相當于只需要承受一分鐘的查詢請求。
當緩存替代數據庫作為主要查詢請求處理者的時候,緩存成為整個系統的瓶頸。
2 減少鎖的粒度
當旅客選擇一張票的時候,我需要鎖定一條記錄,避免同時更新,造成重復出票(這里說以下,我記得大學的時候,我從武漢買回家的票,鐵道部一個座位賣出三張票,而且是大面積情況,相當于一個車廂人數比平時多了三倍,當初我以為是假票,現在看來,可能是重復出票了)。還是拿Z27距離,假設我要買20120931日期票,我必須要選擇一個座位,那么設計的時候,就可以 按照日期,車次,席位類型 三者確定一條記錄,然后鎖定它。而不是值根據車次 + 日期,這樣在你買坐票的時候,買臥鋪的旅客就不會受到影響。(PS:實際鐵道部售票會遠遠這個模型簡單,因為涉及到始發站,停靠站,終點到,假設一個車次停靠 S1->S2-S3,那么旅客買S1->S2 和 旅客買S2->S3 就不會收到影響,我們先簡化模型,這里只是先提出設計的思想)
3 減少訂票處理事務時間
在整個訂票業務流程中,發郵件,發短信,計算各個站點的余票信息等比較等耗時業務操作,完全異步化處理。只需要找出關鍵的流程,如果需要保持一個事務,那么通過異步確保的方式進行。這個是技術層面的東西,后面在介紹。
存在的問題
通過分析,我們給出了一個最簡單的訂票數據庫這一層的解決方案,再仔細分析其中存在的問題
1 余票查詢的維度并不是 車次 + 日期 + 席位類型,應該還有始發站-->終點站因素,必須有一個非常快的算法,判斷是否有票,然后告知應用。
2 緩存是系統中的單點,一旦緩存故障,數據庫估計承受不了
3 數據庫上次我說的只需要分成一個庫(因為上一章節建立數據庫備份機制,故這里面不存在單點),這里面可能存在性能,這里需要進行壓力測試,模擬測試。看看容量上線。為了繼續進行設計,我們假設即使在緩存存在的情況,數據庫沒有辦法處理當前的數據,主要是為了應付春運。
這里先解決余票庫分庫的問題,分庫考慮的原則在上一篇文章分析過,但是由于這個庫的數據量不大,只是訪問會比較頻繁,我們竟可能減少用戶的訪問為主要考慮因素,鐵路購票有其特殊的因素,比如春節的時候從上海買去成都的票非常緊張,查詢量也是***,但是相反,這段期間買成都去上海的票的人就會比較少,查詢量比較少。而春節過后上班也就相反。這個思路也就是說按照站站來分,也可以按照鐵路局賣的票來分。我們的思路就是盡量各個庫的訪問量均勻。不過也存在一個問題,就是分庫的擴展性比較差,一旦擴容,就要做改動。
在談緩存的問題,一臺緩存服務器不夠,可以部署緩存集群。至于是不是一臺緩存服務器存放所有的數據,還是要看數據的多少,盡可能的所有數據都緩存在一臺服務器上面。緩存的數據維度為 預售期、車次 、席位、始發站、終點站 、是否有票 ,按照道理,應該可以緩存所有的數據,不過這個也要看緩存的實現支持***的內存數量。比如java實現的緩存 在32位機器上面 只能支持差不多2G的緩存空間。這里面假設一臺緩存服務器能夠實現所有預售期車票數據的緩存,那么這里面只需要的就是在余票數據更新的時候更新所有集群的緩存數據。
而余票的計算則是里面最為復雜的了,因為新增了兩個維度,就是始發站->到達站。這個問題比較復雜,先暫時放到下一篇文章區分析。
繼續分析,發現上面的分析中,貌似還少了一個比較重要的因素,那就是渠道因素,我們知道,訂票有窗口渠道,代理售票口,網站,電話等等。假如每個渠道售出的票都是公平的,那么肯定不合乎道理的,那互聯網可能就是比較占優勢的(如果系統設計的足夠好的話),對于辛苦排隊的人來說,相當不公平。這里面有兩種解決方案
1 可以設計為每個渠道進行配額,比如網絡訂票 ,我給總票數的多少,每個代理點,我給的票數是多少等等。如果把這些因素在加入到余票信息中,會變的非常復雜,也不好擴展,畢竟這個是屬于經常變化的。設計的一個原則分離不變和易變。如果不變的和易變柔和在一起,系統的擴展性就回很弱。
2 可以按照請求排隊,按照渠道優先級進行分配,這樣在大多數請求排隊的情況下,有一些請求就回被餓死,也就是部分渠道根本買不到票,因為請求會被餓死。
如果要我選擇兩種方案的,我會選擇***種,因為可以在不同時刻進行放票,這樣可以分散請求。來自互聯網10點放票,窗口的8點放票,代理點9點。自然把流量就分開了。渠道配額管理這一塊我覺得將會是最復雜的一個系統,涉及到利益太多。余票庫這里面我想設計的簡單一點,不想把復雜的渠道,配額管理引入進來,盡量放在外圍系統中控制。
今天先寫到這里,發現在這上面思考的比較多了,后面再持續分析吧。還要繼續開發我的ios app,寫文章有點超時了。
原文鏈接:http://www.cnblogs.com/aigongsi/archive/2012/09/18/2689868.html
【編輯推薦】
- ASP.NET Web開發框架項目介紹
- YQBlog .NET MVC3博客系統之用戶系統實戰
- ASP.NET Cache的一些總結
- ASP.NET中常用的幾種身份驗證方式
- 各自為政:ASP.NET實現團隊分工的思考