由 12306.cn 談談高併發+高負載網站性能技術

https://www.cnblogs.com/happyday56/p/3854400.html

12306.cn 網站掛了,被全國人民罵了。我這兩天也在思考這個事,我想以這個事來粗略地和大家討論一下網站性能的問題。因爲倉促,而且完全基於本人有限的經驗和了解, 所以,如果有什麼問題還請大家一起討論和指正。(這又是一篇長文,只討論性能問題,不討論那些用戶界面、用戶體驗、或是是否把支付和購票下單環節分開的功 能性的東西)

甲、認識業務的特殊性

任何技術都離不開業務需求,所以,要說明性能問題,首先還是想先說說業務問題。

  • 其一有人可能把這個東西和扣扣或是網遊相比。但我覺得這兩者是不一樣的,網遊和扣扣在線或是登錄時訪問的更多的是用戶自己的數據,而訂票系統訪問的是中心的票量數據,這是不一樣的。不要覺得網遊或是扣扣能行你就以爲這是一樣的。網遊和扣扣的後臺負載相對於電子商務的系統還是簡單。
  • 其二有人說春節期間訂火車的這個事好像網站的秒殺活動。的確很相似, 但 是如果你的思考不在表面的話,你會發現這也有些不一樣。火車票這個事,還有很多查詢操作,查時間,查座位,查鋪位,一個車次不 行,又查另一個車次,其伴隨着大量的查詢操作,下單的時候需要對數據庫操作。而秒殺,直接殺就好了。另外,關於秒殺,完全可以做成只接受前N個用戶的請求 (完全不操作後端的任何數據, 僅僅只是對用戶的下單操作log),這種業務,只要把各個服務器的時間精確同步了就可以了,無需在當時操作任何數據庫。可以訂單數夠後,停止秒殺,然後批 量寫數據庫。火車票這個豈止是秒殺那麼簡單。能不能買到票得當時告訴用戶啊。
  • 其三有人拿這個系統和奧運會的票務系統比較。我覺得還是不一樣。雖然奧運會的票務系統當年也一上線就廢了。但是奧運會用的是抽獎的方式,也就是說不存在先來先得的搶的方式,而且,是事後抽獎,事前只需要收信息,事前不需要保證數據一致性,沒有鎖,很容易水平擴展。
  • 其四訂票系統應該和電子商務的訂單系統很相似,都是需要對庫存進 行:1)佔住庫存,2)支付(可選),3)扣除庫存的操作。這個是需要有一致性的檢查的,也就是在併發時需要對數據加鎖的。B2C的電商基本上都會把這個 事幹成異步的,也就是說,你下的訂單並不是馬上處理的,而是延時處理的,只有成功處理了,系統纔會給你一封確認郵件說是訂單成功。我相信有很多朋友都收到 認單不成功的郵件。這就是說,數據一致性在併發下是一個瓶頸
  • 其五鐵路的票務業務很變態,其採用的是突然放票,而有的票又遠遠不夠 大家分,所以,大家纔會有搶票這種有中國特色的業務的做法。於是當票放出來的時候,就會有幾百萬人甚至上千萬人殺上去、查詢、下單。幾十分鐘內,一個網站 能接受幾千萬的訪問量,這個是很恐怖的事情。據說12306的高峯訪問是10億頁面訪問量,集中在早8點到10點,每秒頁面訪問量在高峯時上千萬。

多說幾句:

  • 庫存是B2C的惡夢,庫存管理相當的複雜。不信,你可以問問所有傳統和電務零售業的企業,看看他們管理庫存是多麼難的一件事。不然,就不會有那麼多人在問凡客的庫存問題了。(你還可以看看《喬布斯傳》,你就知道爲什麼Tim會接任Apple的CEO了,因爲他搞定了蘋果的庫存問題)
  • 對於一個網站來說,瀏覽網頁的高負載很容易搞定,查詢的負載有一定的難度去處理,不過還是可以通過緩存查詢結果來搞定,最難的就是下單的負載。因爲要訪問庫存啊,對於下單,基本上是用異步來搞定的。去年雙11節,淘寶的每小時的訂單數大約在60萬左右,京東一天也才能支持40萬(居然比12306還差),亞馬遜5年前一小時可支持70萬訂單量。可見,下訂單的操作並沒有我們相像的那麼性能高。
  • 淘寶要比B2C的網站要簡單得多,因爲沒有倉庫,所以,不存在像B2C這樣有N個倉庫對同一商品庫存更新和 查 詢的操作。下單的時候,B2C的 網站要去找一個倉庫,又要離用戶近,又要有庫存,這需要很多計算。試想,你在北京買了一本書,北京的倉庫沒貨了,就要從周邊的倉庫調,那就要去看看瀋陽或 是西安的倉庫有沒有貨,如果沒有,又得看看江蘇的倉庫,等等。淘寶的就沒有那麼多事了,每個商戶有自己的庫存,庫存分到商戶頭上了,反而有利於性能。
  • 數據一致性纔是真正的性能瓶頸。有 人說nginx可以搞定每秒10萬的靜態請求,我不懷疑。但這只是靜態請求,理論值,只要帶寬、讀寫性能夠強、服務器計算能力夠,並支持的併發連接數頂得住 10萬TCP鏈接的建立的話,那沒有問題。但在加上數據一致性這一要求,這10萬就完完全全成了一個可望不可及的理論值了。

我說那麼多,我只是想從業務上告訴大家,我們需要從業務上真正瞭解春運鐵路訂票這樣業務的變態之處。

乙、前端性能優化技術

要解決性能的問題,有很多種常用的方法,我在下面列舉一下,我相信12306這個網站使用下面的這些技術會讓其性能有質的飛躍。

一、前端負載均衡

通過DNS的負載均衡器(一般在路由器上根據路由的負載重定向)可以把用戶的訪問均勻地分散在多個Web服務器上。這樣可以減少Web服務器的請求 負載。因爲http的請求都是短作業,所以,可以通過很簡單的負載均衡器來完成這一功能。最好是有CDN網絡讓用戶連接與其最近的服務器(CDN通常伴隨 着分佈式存儲)。(關於負載均衡更爲詳細的說明見“後端的負載均衡”)

二、減少前端鏈接數

我看了一下12306.cn,打開主頁需要建60多個HTTP連接,車票預訂頁面則有70多個HTTP請求,現在的瀏覽器都是併發請求的。所以,只 要有100萬個用戶,就會有6000萬個鏈接,太多了。一個登錄查詢頁面就好了。把js打成一個文件,把css也打成一個文件,把圖標也打成一個文件,用 css分塊展示。把鏈接數減到最低。

—— 注:以前撥號連接時代,因爲帶寬小,大圖片沒下載完,用戶已經關閉了瀏覽器頁面,所以對連接數要求不高,但對一次的下載量要求很高。但現在帶寬已經足夠, 一次的下載量已經不再是瓶頸,所以,不能再以過去那種方式處理這個問題,而應該合併文件,儘量減少請求數,從而減少交互頻率,把節約出的交互資源用於網站 和用戶的其他更有價值的互動上。

三、減少網頁大小增加帶寬

這個世界不是哪個公司都敢做圖片服務的,因爲圖片太耗帶寬了。現在寬帶時代很難有人能體會到當撥號時代做個圖頁都不敢用圖片的情形(現在在手機端瀏 覽也是這個情形)。我查看了一下12306首頁的需要下載的總文件大小大約在900KB左右,如果你訪問過了,瀏覽器會幫你緩存很多,只需下載10K左右 的文件。但是我們可以想像一個極端一點的案例,1百萬用戶同時訪問,且都是第一次訪問,每人下載量需要1M,如果需要在120秒內返回,那麼就需要,1M * 1M /120 * 8 = 66Gbps的帶寬。很驚人吧。所以,我估計在當天,12306的阻塞基本上應該是網絡帶寬,所以,你可能看到的是沒有響應。後面隨着瀏覽器的緩存幫助 12306減少很多帶寬佔用,於是負載一下就到了後臺,後臺的數據處理瓶頸一下就出來。於是你會看到很多http 500之類的錯誤。這說明服務器垮了。

四、前端頁面靜態化

靜態化一些不常變的頁面和數據,並壓縮一下(比如gzip,當然,也要考慮壓縮和讀取的資源消耗問題)。還有一個變態的方法是把這些靜態頁面放在內存,也就是(/dev/shm目錄下),直接從內存中把文件讀出來返回,這樣可以減少對磁盤的讀寫

注:因爲磁盤讀寫相對於內存讀寫來說太慢。

五、優化查詢

很多人查詢都是在查一樣的,完全可以用反向代理合並這些併發的相同的查詢。這樣的技術主要用查詢結果緩存來實現,第一次查詢走數據庫獲得數據,並把 數據放到緩存,後面的查詢統統直接訪問高速緩存。爲每個查詢做Hash,使用NoSQL的技術可以完成這個優化。(這個技術也可以用做靜態頁面)

對於火車票量的查詢,個人覺得不要顯示數字,就顯示一個“有”或“無”就好了,這樣可以大大簡化系統複雜度,並提升性能。

六、緩存的問題

緩存可以用來緩存動態頁面,也可以用來緩存查詢的數據。緩存通常有那麼幾個問題:

1)緩存的更新方式:也叫緩存和數據庫的同步。有這麼幾種方法,一是緩存過時失效(也就是time out),在經過一定時間後,讓緩存失效,重查;二是,由後端通知更新,一旦後端發生變化,通知前端更新。前者實現起來比較簡單,但實時性不高,後者實現 起來比較複雜 ,但實時性高。

2)緩存的換頁。內存可能不夠,所以,需要把一些不活躍的數據換出內存,這個和操作系統的內存換頁和交換內存很相似。FIFO、LRU、LFU都是比較經典的換頁算法。相關內容參看Wikipeida的緩存算法

3)緩存的重建和持久化。緩存在內存,系統總要維護,所以,緩存就會丟失,如果緩存沒了,就需要重建,如果數據量很大,緩存重建的過程會很慢,這會影響生產環境,所以,緩存的持久化也是需要考慮的。

諸多強大的NoSQL都很好支持了上述三大緩存的問題。

丙、後端性能優化技術

前面討論了前端性能的優化技術,於是前端可能就不是瓶頸問題了。那麼性能問題就會到後臺數據上來了。下面說幾個後臺常見的性能優化技術。

一、冗餘數據

允許數據庫出現冗餘數據,具體方法就是減少表連接這樣的開銷比 較大的操作,但這樣會犧牲數據的一致性,風險比較大。很多人把NoSQL用做數據,快是快了,因爲數據冗餘了,但這對數據一致性有大的風險。這需要根據不 同的業務進行分析和處理。(注意:用關係型數據庫很容易移植到NoSQL上,但是反過來從NoSQL到關係型就難了)

二、鏡像數據

幾乎所有主流的數據庫都支持鏡像(replication)。數據庫的鏡像帶來的好處就是可以做負載均衡。把一臺數據庫的負載均分到多臺上,同時又保證了數據一致性(Oracle的SCN)。最重要的是,這樣還可以有高可用性,一臺廢了,還有另一臺在服務。

數據鏡像的數據一致性可能是個問題,所以我們要在單條數據上進行數據分區,也就是說,把一個暢銷商品的庫存均分到不同的服務器上,如,一個暢銷商品有1萬的庫存,我們可以設置10臺服務器,每臺服務器上有1000個庫存,這就好像B2C的倉庫一樣。

注:數據鏡像一致性問題其實沒那麼好解決,日常現實中經常遇到某一後操作失誤,需要恢復到前操作,但因爲系統本身是自動化處理,往往只注意到數據完整性這種問題,而沒法注意到數據對人的意義問題,從而可能這樣的一致性機制反而會造成現實的災難性,所以,還必須設置一條人工的確認操作(可以有多種方式,比如用戶的訂單提交再次確認),否則數據一致性會令人抓狂

三、數據拆分(數據分區)

數據鏡像不能解決的一個問題就是數據表裏的記錄太多,導致數據庫操作太慢。所以,把數據拆分開來。數據拆分有很多種做法,一般來說有下面這幾種:

1)把數據把某種邏輯來分類。比如火車票的訂票系統可以按各鐵路局來分,可按各種車型分,可以按始發站分,可以按目的地分……,反正就是把一張表拆成多張有一樣的字段但是不同種類的表,這樣,這些表就可以存在不同的機器上以達到分擔負載的目的。

2)把數據按字段分,也就是堅着分表。比如把一些不經常改的數據放在一個表裏,經常改的數據放在另一個表裏。把一張表變成1對1的關係,這樣,你可 以減少表的字段個數,同樣可以提升一定的性能。另外,字段多會造成一條記錄的存儲會被放到不同的頁表裏,這對於讀寫性能都有問題。

3)平均分表。因爲前兩種種方法是並不一定平均分均,可能某個種類的數據還是很多。所以,也有采用平均分配的方式,通過主鍵ID的範圍來分表。

4)同一數據拆開存放。這個在上面數據鏡像提過。也就是把同一商品的庫存值分到不同的服務器上,比如有10000個庫存,可以分到10臺服務器上,一臺上有1000個庫存。然後負載均衡。

這四種分區都有好有壞。最常用的還是第一種。

注:數據一旦拆分存放,就需要有一個或是多個調度來讓你的前端程序知道去哪裏找數據,這種調度會消耗反應時間,因此,不可分得太細,調度表和拆開來的表大小比例如何?

把火車票的數據分區,並放在各個省市,會對12306這個系統有非常有意義的質的性能的提高

四、後臺系統負載均衡

前面說了數據拆分存放,數據拆分存放可以在一定程度上減輕系統負載,但是無法減輕熱銷商品的負載,對於火車票來說,可以認爲是大城市的某些主幹線上 的車票。這就需要鏡像數據來減輕負載。鏡像了數據,必然要用到負載均衡,在後臺,我們可能很難使用像路由器上的負載均衡器,因爲那是均衡流量的,因爲流量 並不代表服務器的繁忙程序。因此,我們需要一個任務分配系統,其還能監控各個服務器的負載情況。

任務分配服務器有一些難點:

  • 負載情況比較複雜。什麼叫忙:是核芯處理量高?還是磁盤讀寫頻率高?還是內存使用量高?還是併發數高?你可能需要全部都要考慮。這些信息要發送給那個任務分配器上,由任務分配器挑選一臺負載最輕的服務器來處理。
  • 任務分配服務器上需要對任務隊列,不能丟任務啊,所以還需要持久化。並且可以以批量的方式把任務分配給服務器。
  • 任務分配服務器死了怎麼辦?這裏需要一些如即時替用(Live-Standby)或是失效備援(failover)等高可用性的技術。我們還需要注意那些持久化了的任務的隊列如果轉移到別的服務器上的問題。

中央分配式:看到有很多系統都用靜態的方式來分配,有的用hash,有的就簡單地輪流分析。這些都不夠好,一個是不能完美地負載均衡,另一個靜態的方法的致命缺陷是,如果有一臺計算服務器死機了,或是我們需要加入新的服務器,對於我們的分配器來說,都需要重新刷新。

下游請纓式:由下游的計算服務器去任務服務器上拿任務。讓這些計算服務器自己決定自己是否要任務。這樣的好處是可以簡化系統的複雜度,而且還可以任意實時地減少或增加計算服務器。但是唯一不好的就是,如果有一些任務只能在某種服務器上處理,這可能會引入一些複雜度。 不過總體來說,這種方法可能是比較好的負載均衡。

五、異步處理、 限流閥 和 批量處理

異步、限流閥(throttle) 和批量處理都需要對併發請求數做隊列處理的。

  • 異步處理:在業務上一般來說就是收集請求,然後延時處理。在技術上就是可以把各個處理程序做成並行的,也就可以水平擴展了。但是異步的技術問題大概有這 些,1)被調用方的結果返回,會涉及進程線程間通信的問題。2)如果程序需要回滾,回滾會有點複雜。3)異步通常都會伴隨多線程多進程,併發的控制也相對麻煩一些。4)很多異步系統都用消息機制,消息的丟失和亂序也會是比較複雜的問題。
  • 限流閥: 這其實是個保護機制,是在不提升性能和資源消耗的情況下,防止系統被超過自己不能處理量給搞垮了,這樣系統看起來會顯得穩健!使用限流閥技術一般來說是對於一些自己無法有效控制或者是需要穩定不中斷運行的系統,比如,和你網站對接的銀行系統,或者是長城防火牆。
  • 批量處理的技術,是把一堆基本相同的請求批量處理。 比如,大家同時購買同一個商品,沒有必要你買一個我就寫一次數據庫,完全可以收集到一定數量的請求,一次操作。這個技術可以用作很多方面。比如節省網絡帶 寬,我們都知道網絡上的最大單位傳輸量(MTU,或者翻譯爲最大傳輸單元,就是一次傳輸的量),以太網是1500字節,光纖可以達到4000 多個字節,如果你的一個網絡包沒有放滿這個最大傳輸單元,那就是在浪費網絡帶寬,因爲網卡的驅動程序只有一塊一塊地讀效率纔會高。因此,網絡發包時,我們需要收集到足夠多的信息後再做網絡吞吐,這也是一種批量處理的方式。批量處理的敵人是流量低,所以,批量處理的系統一般都會設置上兩個閥值,一個是作業量達標,另一個是自動過時失效,只要有一個條件滿足,就會開始提交處理。

所以,只要是異步,一般都會有限流閥機制,一般都會有隊列來排隊,有隊列,就會有持久化,而系統一般都會使用批量的方式來處理

雲風同學設計的“排隊系統” 就是這個技術。這和電子商務的訂單系統很相似,就是說,我的系統收到了你的購票下單請求,但是我還沒有真正處理,我的系統會跟據我自己的處理能力來限制住這些大量的請求,並一點一點地處理。一旦處理完成,我就可以發郵件或短信告訴用戶你來可以真正購票了。

在這裏,我想通過業務和用戶需求方面討論一下雲風同學的這個排隊系統,因爲其從技術上看似解決了這個問題,但是從業務和用戶需求上來說可能還是有一些值得我們去深入思考的地方:

1)隊列的DoS攻擊。首先,我們思考一下,這個隊是個單純地排隊的嗎?這樣做還不夠好,因爲這樣我們不能杜絕黃牛,而且單純的ticket_id很容易發生DoS攻擊,比如,我發起無數個 ticket_id,進入購票流程後,我不買,我就耗你半個小時,很容易我就可以讓想買票的人幾天都買不到票。有人說,用戶應該要用身份證來排隊, 這樣在購買裏就必須要用這個身份證來買,但這也還不能杜絕黃牛排隊或是號販子。因爲他們可以註冊無數個帳號來排隊,但就是不買。黃牛這些人這個時候只需要幹一個事,把網站搞得正常不能訪問,讓用戶只能通過他們來買。

注:可見,除了限流閥機制,還要一個過期機制,外加一些輔助檢測手段(比如需要認證的登陸)——其實,此處比拼的就是網站和黃牛的硬件實力了,只要能夠把門檻提高到黃牛的硬件投入超越邊際效益臨界點,即可。

2)對列的一致性?對這個隊列的操作是不是需要鎖?只要有鎖,性能一定上不去。試想,100萬個人同時要求你來分配位置號,這個隊列將會成爲性能瓶頸。你一定沒有數據庫實現得性能好,所以,可能比現在還差

3)隊列的等待時間。購票時間半小時夠不夠?多不多?要是那時用戶正好不能上網呢?如果時間短了,用戶也會抱 怨,如果時間長了,後面在排隊的那些人也會抱怨。這個方法可能在實際操作上會有很多問題。另外,半個小時太長了,這完全不現實,我們用15分鐘來舉例:有 1千萬用戶,每一個時刻只能放進去1萬個,這1萬個用戶需要15分鐘完成所有操作,那麼,這 1千萬用戶全部處理完,需要1000*15m = 250小時,10天半,火車早開了。(我並亂說,根據鐵道部專家的說明:這幾天,平均一天下單100萬,所以,處理1000萬的用戶需要十天。這個計算可能有點簡單了,我只是想說,在這樣低負載的系統下用排隊可能都不能解決問題

注: 這說法有點問題:首先,一趟車的載人數是有限的,所以並沒有必要處理1千萬用戶的需求,可以通過設定提前購票天數,來分流這些人員。但按照春運總十億人次 來算的話,如果一次能放進10萬個,10萬×15分鐘=25000小時,則需要1042天處理完,如果一次能放進100萬,則需要104天處理完,還是不 夠,春運沒有那麼多天,一次需要放進1000萬,纔夠!

4)隊列的分佈式。這個排隊系統只有一個隊列好嗎? 還不足夠好。因爲,如果你放進去的可以購票的人如果在買同一個車次的同樣的類型的票(比如某動車臥鋪),還是等於在搶票,也就是說系統的負載還是會有可能 集中到其中某臺服務器上。因此,最好的方法是根據用戶的需求——提供出發地和目的地,來對用戶進行排隊。而這樣一來,隊列也就可以是多個,只要是多個隊 列,就可以水平擴展了。

我覺得完全可以向網上購物學習。在排隊(下單)的時候,收集好用戶的信息和想要買的票,並允許用戶設置購票的優先級,比如,A車次臥鋪買 不到就買 B車次的臥鋪,如果還買不到就買硬座等等,然後用戶把所需的錢先充值好,接下來就是系統完全自動地異步處理訂單。成功不成功都發短信或郵件通知用戶。這樣,系統不僅可以省去那半個小時的用戶交互時間,自動化加快處理,還可以合併相同購票請求的人,進行批處理(減少數據庫的操作次數)。這種方法最妙的事是可以知道這些排隊用戶的需求,不但可以優化用戶的隊列,把用戶分佈到不同的隊列,還可以像亞馬遜的心願單一樣,讓鐵道部做車次統籌安排和調整(最後,排隊 系統(下單系統)還是要保存在數據庫裏的或做持久化,不能只放在內存中,不然機器一掛,就等着被罵吧)。

丁、小結

寫了那麼多,我小結一下:

0)無論你怎麼設計,你的系統一定要能容易地水平擴展。也就是說,你的整個數據流中,所有的環節都要能夠水平擴展。這樣,當你的系統有性能問題時,“加3倍的服務器”纔不會被人譏笑。

1)上述的技術不是一朝一夕能搞定的,沒有長期的積累,基本無望。

2)集中式的賣票很難搞定,使用上述的技術可以讓訂票系統能有幾佰倍的性能提升。而在各個省市建分站,分開賣票,是能讓現有系統性能有質的提升的最好方法。

3)春運前夕搶票且票量供遠小於求這種業務模式是相當變態的,讓幾千萬甚至上億的人在某個早晨的8點鐘同時登錄同時搶票的這種業務模式是變態中的變態。業務形態的變態決定了無論他們怎麼辦幹一定會被罵。

4)爲了那麼一兩個星期而搞那麼大的系統,而其它時間都在閒着,也就是鐵路才幹得出來這樣的事了。

 

戊、附錄:

一、雲風方案:

其實鐵路訂票系統面臨的技術難點無非就是春運期間可能發生的海量併發業務請求。這個加上一個排隊系統就可以輕易解決的。

本來我在 weibo 上閒扯兩句,這麼簡單的方案,本以爲大家一看就明白的。沒想到還是許多人有疑問。好吧,寫篇 blog 來解釋一下。

簡單說,我們設置幾個網關服務器,用動態 DNS 的方式,把併發的訂票請求分攤開。類比現實的話,就是把人分流到不同的購票大廳去。每個購票大廳都可以買到所有車次的票。OK ,這一步的負載均衡怎麼做我就不詳細說了。

每個網關其實最重要的作用就是讓訂票的用戶排隊。其實整個系統也只用做排隊,關於實際訂票怎麼操作,就算每個網關後坐一排售票員,在屏幕上看到有人來買票,輸入到內部訂票系統中出票,然後再把票號敲回去,這個系統都能無壓力的正常工作。否則,以前春運是怎麼把票賣出去的?

我們來說說排隊系統是怎麼做的:

其實就類似我們去熱門館子吃飯拿號。只不過要防止別人僞造號插隊而已。

如果你來一個人(一次 HTTP 請求),我就隨機產生一個我做過一些簽名處理的號碼返回給你。暫時稱爲 ticket id 。這個 ticked id 是很難僞造的。

系統在內存裏開一個大數組(32G 內存夠排上億人了吧),就是一循環隊列。把這個 ticket id 放在隊列尾。

用戶現在拿着 ticket id 向網關發起請求。網關利用一次 hash 查詢,在內存中的數組隊列裏查到它的位置,立刻返回給用戶。用戶的前端就可以看到,他在這個網關(售票大廳)前面還有多少人等着。

這裏的關鍵是,整個隊列都在本機的內存中,查詢返回隊列中的位置,可以實現的比一個處理靜態文件的 web server 還要高效。靜態文件至少還要去調用文件 IO 呢。靜態文件 web server 可以處理多少併發量,不用我介紹了。

同時,前端會控制用戶拿着 ticket id 查詢隊列位置的頻率。高負載時可以 1s 一次,甚至更長時間。爲了防止用戶自己寫腳本刷這個請求(雖然沒有太大意義,因爲刷的多也不會排到前面去),如果見到同一個 ticket id 過於頻繁的查詢。比如 10s 內查詢了 20 次以上。就直接把這個 ticket id 作廢。持有這個 ticket 的人就需要重新排隊了。

對於最後排到的人,系統會生成一個唯一的不可僞造的 session id ,用戶下面就可以通過這個 session id 去做實際的購票流程了。可以連去真正的購票服務器,也可以通過網關中轉。非法的 session id 會立刻斷掉,用戶一旦知道僞造 session id 幾乎不可能,只有通過 ticket id 排隊拿到,除非是惡意攻擊系統,不然不會有人亂拿 session id 去試。

我們再給每個 session id 設置一個最長有效時間,比如半小時。如果超過半小時還沒有完整購票流程,那麼就重新去排隊。

至於同時開放多少個 session id ,也就是相當於開放多少個購票窗口,就取決於購票系統能承受的負載了。不過簡單計算一下,就知道有排隊系統保證了良好的次序,再以計算機的吞吐能力,解決 不過幾億人的購票請求,即使這些人都同來排隊,也就是一組機器幾小時的處理量而已。

這票 blog 也就是隨便寫寫,可能不太嚴謹,但意思達到了。中間有很多數據需要估算,也不是太難的事情。

爲什麼現在的購票系統這麼濫?關鍵在於大量的網絡帶寬,計算力浪費在了“維持次序”上。系統不穩定時,大量的只做了一半的無效的購票流程浪費掉了這 些。要響應高併發的 HTTP 請求,關鍵就在於迅速反應,不要什麼都想着從數據庫繞一圈。排隊的隊伍維持就完全不需要使用數據庫。如果所有 HTTP 請求都立刻返回,在短時間內可以處理的 HTTP 請求量也會非常大。而如果你一下處理不了這個請求,又把 TCP 連接保持在那裏,就莫怪系統支持不住了。

另外,用戶看到了不斷在減少的隊列前面的人數,他們也會安心等待。只要網站頁面刷新流暢(只處理隊列信息很容易保證),用戶體驗會很好。


最後補充幾句廢話:因爲鐵路購票系統很多年前就實現了內部網絡化,有成熟系統支撐,運作多年。這次做互聯網版本,一定不能放棄原有系統新來一套。不然實體購票點也在網頁上刷不出票就崩潰了。

所以要做的僅僅是怎麼做一個系統和原有系統對接。這樣風險最小。兩套系統可以分別優化處理能力。基於這個設計起點,所以我纔不看好所有企圖取代原有系統的方案。

再補充:

1、、排隊前把身份證填好,系統編碼到 ticket 裏去。

排到了這個 ticket 只可以用這個身份證買一張或幾張同車次的票。

註冊環節完全可以免了。如果需要註冊,填個密碼就 OK。用戶名可以暫時用身份證,買完票可以換,或者去別的服務器上換,不干擾購票流程。

你 8 點出票可以 7 點開始排。系統反正認定你了沒人插隊。現實就如此。不要去想比同樣跟你一樣需要票的人更有優先權。

 

 

======================================================================
二、其他網友看法:
1、唉,一個網站在大數據量訪問是掛掉,是很正常的現象,有什麼好炒作的!而且數據量不是幾萬,而是幾億!!!掛掉不丟人!!!!!
2、訂票其實還有一個特點:各車次的分管鐵路局是固定的!如果按鐵路局將系統分割,負載將大大分散。就如10086.cn一樣,他會要求你先輸入手機號後跳轉到各個分公司的系統。——但這樣異地購票或者使用異地手機的就麻煩了!
3、“網遊和扣扣在線或是登錄時訪問的更多的是用戶自己的數據”,非遊戲從業者,不過感覺遊戲裏某些公共事件都是在讀寫公共資源,有時候規模也不小呢;不過副本技術確實極大的縮小了這個規模。
4、文中提到可以使用緩存,比如現在12306是10分鐘更新一次,緩存每10分鐘都會失效,刷新緩存帶來的峯值要怎麼處理?
5、用排隊+配額申請制,
一句話“更新”(update)轉換成“插入”(insert)操作, 非要說像的話, 這像個廣告系統
6、放票模式不做更改,運力不提升,單靠優化網站還是被罵。
假設鐵道部有能力優化,那麼可以預見,在很短的時間內,票就被賣出去了,沒有買到票的人就會罵鐵道部,票都被黃牛買了。
7、 我覺得需要注意一點火車票和電子商務的不同,車次是有限的,不像淘寶這樣商品無限多,而且業務邏輯簡單得多。這 樣可以很容易的按照車次分區,每個車次是單獨的隊列,甚至完全可以放在內存,不同車次間不存在鎖的問題,即使同車次也可以優化鎖粒度。另外,查詢結果沒必 要可刻意要求和實際票務情況同步一致,可以誤差一秒鐘,查車次和查餘票分開,這樣90%的查詢時可以緩存的。假設車票在開票10分鐘內就被搶完, 根據每列車的座位數和每個座位被賣的次數,可以估算每個車次,每秒要處理幾個請求,實際也沒幾個。

另外不同於電子商務的庫存,每個座位可以被賣幾次,用位運算實現很簡單。

人們之所以用外掛刷票,因爲網站爛,登陸難,下單難,付款難等等,如果訂票過程流暢,外掛自然就沒人用了,再加上簡單可靠的防刷新和限流措施,應付現在的量沒問題,鐵道部的數據不可信,看他們說他們處理的是世界頂尖的難題就知道。

8、 技術方面不懂,不過我認爲對於12306網站的庫存管理,和普通B2C的電商庫存管理,有很多不一樣的地方

(1). 春運期間,運力不足,訂單始終大於庫存,但庫存是固定的,即車票的最大限額。
(2).閒暇時間,訂單小於庫存,但車次固定,座位固定,庫存也是固定的,即便是一個乘客,也會開一班列車
鐵道部幾乎不用考慮訂單和庫存之間的供求關係,普通電商過於看重庫存管理,是因爲害怕庫存積壓過多,直接影響到企業資金鍊的運行,而這個問題對於鐵道部根本不存在,鐵道部只要考慮怎麼把固定的車票數量快速,流暢,均勻的分發到每位乘客手裏就可以了。

而且我感覺,這麼多年應付春運的經驗,鐵道部對於減輕售票壓力的方案也會很成熟的,不過肯定都沒有用在這個網站上面上。

9、因爲無票了,用戶不甘心,反覆刷——訪問壓力大10倍

因爲網頁慢、死,所以用戶不停的刷——訪問壓力大100倍
這是惡性循環:越是慢,訪問量越大

如果,網頁流暢了,哪怕還是無票,就不存在這99倍的刷新了,而只是10倍的刷新
這是良性循環:越是快,訪問量反而少了

10、鐵老大做了一件費力不討好的事情,這事情直接拿30%的票給淘寶,30%給騰訊,自己留剩下賣,這一天幾千萬的點擊量是個巨大的廣告收入啊,自己不花一個子,衝別人大口要錢,這樣自己花了幾千萬做了個被萬人馬的東西,不如自己不花錢,輕鬆拿錢,不行還罵別人。
直接找阿里巴巴或騰訊這樣的戰略伙伴,垂拱而治,何樂而不爲呢?
11、帶寬、核芯、存儲都不是緊缺資源,一切的根源在於票太少人太多。一到春運運量能翻好幾倍,運力只能提升一倍還不到。更好的系統是必須的,但是買不到票還是捱罵。
一個可以考慮的辦法是:限制300公里以內的短程票,分流到公路客運去,這樣可以緩解一點點壓力。
最好能依據統計分析來確定:最小的瓶頸在哪。
12、查詢和下單等操作用到的數據,應該是從鐵道部客票系統已有的數據接口來直接獲取數據。12306.cn和客票系統共享以前的老數據庫。老舊的系統,必然適應不了現在的春運的變態需求。
13、票量=票量-1的時候——那麼是不是隱含這個意思:針對某個車次有一條數據庫記錄用於登記剩餘的車票,導致訂購某車次的所有訂單都要等待鎖定這條記錄並且更新?如果是這麼設計的, 確實有點那啥。一般來說一個車次的票量有限,完全可以用一個內存中的test-and-set(reduce)的操作來進行控制,熱門車次的票量很快就會降低到零,後面的訂單都test不過去,可以直接拒絕了。
(1)數據庫鎖不可怕,大量訂單都要鎖定同一資源纔可怕
(2)用數據庫的鎖來做票量更新和統計,帶上事務就要慢好幾拍;如果這個票量更新要和每筆訂單的事務合併提交,那就更要慢上N倍。
————但是,車次那麼多,這樣的內存量也會非常驚人!!!!
14、併發下的數據一致性。放在內存裏也一樣要上鎖做互斥。
15、我覺得很多人沒用過這個網站就來發表意見,這個網站雖然反覆的提示用戶太多稍後再試,可是絕對沒有說掛掉或者慢的情況。只要登陸進去,訂單成功提交,後續 過程基本沒有頁面慢死的情況。估計他是直接從請求中抽取一部分人登陸來控制在線用戶數量,然後再提交的訂單中抽出一部分進入付款來控制售票速度。————也就是說,本身已經使用了限流閥的方法了。
16、這樣一個系統如何實現相對公平性,特別是這次對於電腦操作不會或較弱的人就不公平。
比如如果系統採用了排隊系統後,不從技術角度考慮, 我這裏還有幾個問題想不通。

(1). 什麼時候開始排隊,因爲大家知道票是上午十點或下午三點出票。
這個隊伍是一直存在,從系統運行時起。還是每天重新排。
如果每天重新排,在重排的那一刻,還在系統裏的人如何理?我想這裏大部分人是踢出來,不然重排就沒意義了。
如果隊伍一直存在,進入的人是可以永遠在裏面,還是有策略的踢出來。這裏 永遠在裏面應該不可能。
如果有策略的踢出來,比如過一小時踢出來,那如果我要買的票在三點出票,我會想我要在兩點多一點進入系統,
而買票的人應該都是這種想法,那我應該什麼時候開始排隊,才能保證我能在兩點多一點進入系統,天哪,感覺這個無解,絕望!

(2). 假如隊伍會在每天清空並在每天三點開始排隊,但在排隊的那一刻起,領到的號靠前的條件是什麼?
應該是手速和網速。如果這樣的話,我們可以看看排在隊伍前面的是些什麼人:
程序刷的, 遊戲高手, IT重業人員, 都市白領, 普通受過教育接觸計算機的人 文化低(對應操作計算機的能力)的外來務工人員。
程序刷可以禁,但效果如何就不清楚了。

如果是這樣的話,與現有系統比,不同之處就在於
現有系統在人們試了無數次後,給人絕望
而排隊後,一開始就給人絕望,你的號碼是1000000,在你的前面還有999999人等待處理。

對於一個具體的會操作電腦的普通人而言:
現有系統:試了無數次,網頁崩潰無數次,拼盡了耐心,終於買到了一張票。
排隊系統: 放棄吧,你在這買不到票

如果開始排隊算作比賽的話,那就是職業運動員和業餘運動員的
當然,我絕對不是在說現有的系統好!我只是在說我們所有的技術都作好了,還是不能從業務角度實現它的合理公平性。
沒事,瞎想的!

17、所以我覺得正確的方法其實是抽獎,在所有請求中抽獎,中獎的登陸入系統,在所有訂單請求中抽獎,中獎的進入支付。這樣比排隊系統公平很多,每個訂單都有中獎的機會。
18、雲風的方案可靠就表現在把現實中的購火車票排隊系統通過網絡來實現, 效率肯定要比現實排隊高多了.
先排到隊, 然後再查詢, 再買票…整體火車購票系統的需求不就是這樣嗎?
另外, 負載小, 壓力小, 實現簡單靠譜.
排到隊的, 每個隊列的後臺用人工一對一服務都處理的過來…
19、我認爲鐵道部的一定都知道上文的所有東西,因爲我這樣一般的人就知道了,也都會放進去考慮,我的系統正好是跨舊系統查詢與追求效能,與鐵道部需求很像,當然流量差很多。
我想主要的問題應該是當初估計尖峯流量是一億,最後來了十億,第二是後端系統承受不住,或是交換資料的部份承受不住,因爲後端系統是內網舊系統,新舊系統 溝通還要兼顧效能會有極大挑戰,就算一般採用異步MQ等方式,但是量那麼大可能也是吃不完,總不能下單後十小時纔給結果。
不過這篇文章很值得想開發大系統的人去思考一下,我想還是要經過這些問題的洗禮後自然就會了,我就是這樣系統搞掛幾次, PM下臺幾個就會了,反正怎麼換也不會換掉技術人員。鐵道部的人下次出來應該也都很強了,沒有經過這些問題真的死過的,也沒人會要你去設計這類的系統。
那麼多人用hibernate的甚至EJB,這樣的系統這類O/RM的我的經驗是擋不住的,還是直接弄memcache/Coherence, 資料庫用 partition table等比較穩
20、我看到的幾點如下:
第一,從訪問結果來看12306.cn已經使用了CDN網絡,提供商名字忘記了,可以查出來
第二,從訪問動態頁面的結果看,12306.cn用的應該是ngnix+Jboss
第三,12306.cn和奧運訂票系統一樣由太極集團製作
第四,從訪問結果看CDN基本上沒出什麼問題,每次500都是由Jboss返回的。個人理解,是jboss掛了
21、現在最大的瓶頸是 提交訂單非常慢,非常困難,其次是支付接口經常出問題;登錄和查詢不是那麼緊。
提交訂單瓶頸的原因我估計是,鐵路內部採用了網絡支付限制,大約是1萬張(忘記從哪裏看到的),但是可能同時就是n多人同時搶票,這樣,客觀造成了一個排隊效應;排在後面的人還要等前面的人去支付,再去更新數據庫,把事務加到裏面了,那是相當慢。
解決問題的首要一步是確定這個系統達到什麼效果,鐵道部定時發票的節奏是不可能改變的,那套數據庫也是不可能改變的,支付銀行你也是沒辦法去改變的。僧多 粥少,刷票也是不可避免的,你也不可能做到人人上去就能買到票。這個系統建設的目的就是,用戶能快速登錄,查詢,如果有票能夠很快買到,或者很快被告知不 被買到,而且能夠避免黃牛。那麼,這個系統就是成功的。
反過來看看,現在的網站呢?用戶無法感知是否成功,明明看了有票就是提交不上,提交上了半天不響應,然後說不讓你買了,好不容易買了交了錢了,支付那邊又超時了。
所以說,我對12306的想法是:
1.找個好的產品經理,像用戶看到票有然後就是提交不上去,一點其他可供感知的東西都沒有,這種設計水平我就不說了;
2.優化網絡流程,例如可以把網絡支付做到網站裏,或者完全是異步支付;網絡放票,可以做成按片不定時放,上午放上海,下午放杭州,隨機放票,避免造成過多的併發;
3.再就是做好負載均衡,共享數據信息之類。其實我覺得查詢之類的可以用阿里雲之類的來搞定啊,春運期間,搞定核心數據流程就OK了。
22、這東西,你讓百度、騰訊、阿里巴巴的團隊去做都不好使。現在那個訂票的web站點固然有很多問題。但是他們的開發人員肯定接觸不到鐵道部的票務核心網,鐵道部最多給他們幾個調用接口,至於接口的響應速度等web站點的架構根本無法涉及。 
這非常類似淘寶在雙11大促銷的時候,淘寶的技術非常好,瀏覽、查詢、交易、訂單,從購物到支付寶到阿里旺旺,沒有任何問題,但是網銀頁面掛了,因爲各大銀行的接口跟不上。
現在就算做一個非常牛逼允許十三億人同時併發的web站點,鐵道部的票務核心系統跟不上,你這個web做再好,也只是個架子。最多就是報錯提示能更人性化一點。
想要讓訂票變的順暢,只有重構鐵道部的票務核心系統,那估計就是一個超大的大工程。
——————現在顯然是web沒做好,後一步卡不卡就不知道了,因爲瓶頸在web了
23、大家都開始把系統的優化改造方案向着真實世界的購票大廳排隊體系思考了,這很好,但真實世界裏一定就會帶來新的人爲因素,例如銀行的排隊,就區分個人用戶、企業用戶、VIP用戶,高級會員啥的, 只要有人敢把12306改成排隊的購票系統,就一定會弄出來不同等級的用戶權限,到時候排隊的優先級、插隊等真實世界的需求就都來了,那時候看大家還 說哪個公平來着。
24、排隊算法的原型是火車站售票廳的排隊買票。 
而網上購票的排隊,有兩種方案選擇,一是用戶先選好從哪裏到哪裏的票,再排隊。二是先排隊,排到你了再選要從哪到哪裏。 
第一種,你可以想象下這種情景。售票廳爲了加快速度,要求所有人排隊前先領個小票,上面寫着從哪到哪,如果排到你的時候沒你寫的票了,就不能買 了,你要回去重新領個小票重選車次和首末站,然後必然得回到隊尾重排,不回到隊尾的話否則你選站會妨礙後面排隊的人,導致領小票再排隊失去意義。 
那買票的人能不造反嗎?他選票時有票的,排到他時沒了,得一次次重選重排,還不如現在售票廳的賣法有效率呢! 

第二種,先排,排到再選票,好了,售票廳有熟練的售票員引導你,你兩三分鐘能買好,而網站,你給多少時間選票呢?一個旅客要先搜出想要的票的即時信息,可能沒有,再搜下一趟,再搜轉車方案。注意提前搜好是沒多大意義的,因爲票信息很快過時,只有排到你時搜出來的纔有現實意義,只能排到再搜。那一個人也得給他三分鐘以上來選好票下單,甚至因爲很多人不熟悉操作,得給更長時間。 
如果這隊伍前進得比火車售票廳還慢,而網上排隊的人又必然比售票廳還多,那這隊你排一天都未必輪得到你,還不可能每個人都坐在電腦前死等,網站排隊變成了笑柄。 
25、我雖然也噴一次12306, 其是因爲填了幾個小時的驗證碼, 有點火,

但是心裏還是佩服的, 至少如此高的頁面訪問量 , 訪問人數量, 網站然後還流暢的運行, 已經很不錯了, (即使是說用戶過多, 那只是後面系統處理不過來的原因)

因爲人太多, 拼命加硬件也可以解決,前面查詢與後面支付問題, 但是隻是每年春節才用一次, 不值當的, 所以我有一個小小的想法來解決這個問題,

我說的這個問題特指: 目前網站前臺接待工作做的很好, 但是把你帶到後堂, 就沒有人招呼了, 就跟我大學的時候, 吃飯的時間大家都拿水瓶去打水, 結果茶房擠破頭, 但是呢最終都有水打, 只是要耗太多的時間在那等, 我就想大家都茶壺放在那, 然後有一個人專門來打水 , 大家都估計(根據送來水壺的時間)水打好了, 就直接到拿走就行了, 節省時間

所以我想到的方法是: 採用預訂機制, (歸根結底是因爲沒有排除)

1. 先查詢出我要訂哪些票(票的價格都有), 此時直接可以支付(註冊時已經認證過身份證信息), 支付好了以後, 大家就該幹嘛幹嘛,
2. 系統根據預訂隊列, 按照系統所能使出最大能力, 生成車票, 如果可能可以短信提醒或郵件提醒訂票成功與否, (如果有客戶選擇的時間範圍,自動滾動到下一批的隊列)
3. 最終還是沒有票, 執行退款動作

我想這樣做的好處, 大家不用無謂的去查詢多張, 再訂啊訂啊, 增加無謂的服務器和帶寬負擔, 這樣就可以把前臺接客的資源節省(甚至支付資源)一大部分, 並用與支付環節

26、很多事情並不是技術可以解決的。

今天是因爲網上購票系統扛不住了導致買票困難,導致罵聲一片,而如果真的是系統很牛叉可以抵擋的了這樣的訪問,那麼新的問題又來了:在放票的一瞬間票就被秒殺光了。

結果會怎樣?
全國人民再次抱怨買不到票,甚至懷疑購票系統。
並且這樣會導致專門有人開發刷票軟件。那樣,再牛X的系統、再好的排隊系統也無濟於事的。。。因爲網上到處充斥着刷票程序。這樣,真正通過人工登錄去購票的人還是買不了票。

我倒是想到了一個非技術的可行辦法

簡單的說就是把預售改成預約,任何人都可以實名制預約某個班次火車,然後截止預約時間後系統對旅客進行虛擬購票,第一批爲分組1,虛擬售罄後再從1車廂1 號座位開始虛擬售票,爲第2組,再售罄後就是第三組,以此類推。假如總共虛擬售出了5組,那麼在通過一個公開的方式選出某一組爲獲得該車次的購票權(比如當天滬深指數之和對總組數的餘數+1即爲該組獲得購買權),然後在規定的天數內付款買票即可。

這樣,即不需要那麼牛叉的並非系統,買票的人也不需要到3點去秒殺火車票了。

27、關於老系統的問題:

最簡單的方式就是新系統分配到一定配額的車票,獨立運行。其實這應該是最佳的方案,否則在正常情況下,網絡的出票效率肯定是高於其他方式,如果不做配額,99%的票都會被網絡拿走。

稍微複雜一點的就是加一個仲裁的服務器,每幾分鐘分別向老系統和新系統放票,新老系統也是獨立運行。

最後,比較笨的方法新系統通過外掛的形式和老系統交互,系統串行化請求,加個流量控制之後向老系統請票,這樣新系統出票速度不會很快,但至少不會讓用戶卡在用戶交互界面上。

28、系統在內存裏開一個大數組(32G 內存夠排上億人了吧),就是一循環隊列。把這個 ticket id 放在隊列尾。

用戶現在拿着 ticket id 向網關發起請求。網關利用一次 hash 查詢,在內存中的數組隊列裏查到它的位置,立刻返回給用戶。用戶的前端就可以看到,他在這個網關(售票大廳)前面還有多少人等着。

黃牛開始賣 tickid 了, 大量的刷,囤這個tickid, 到時候賣給別人就可以了。

這個設計不靠譜。




======================================================================
黑傳說看法:
原文已經被我改造過,爲方便理解,添加了註解(斜體字),用通俗的詞彙替換了一些名詞(一般會在括號里加注專用名詞),並對一些說法做了通俗化的更改。
說到底三個問題:
1、火車本身的運力問題:這是根本的問題,其他都是輔助
2、網站硬件上可能需要繼續投入,畢竟這是一個人口大國的高交互量、需要高穩健度的網站
3、業務模式可以稍作調整,這樣不僅改善了用戶體驗,而且還節約了系統資源開銷。比如排隊,可以排到若干人再完成總交易,這樣可以控制在自己的處理範圍內。

 

轉載 http://coolshell.cn/articles/6470.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章