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