日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 電腦知識 正文

12306搶票,極限并發帶來的思考

更新時間: 2019-11-05 電腦知識

那些年熬夜刷的12306經過多年迭代,承受著這個世界上任何秒殺系統都無法超越的 QPS,上百萬的并發再正常不過。那么,系統如何在 100 萬人同時搶 1 萬張火車票時,提供穩定的服務?

12306 搶票 極限并發

高并發的系統架構都會采用分布式集群部署,服務上層有著層層負載均衡,并提供各種容災手段(雙火機房、節點容錯、服務器災備等)保證系統的高可用,流量也會根據不同的負載能力和配置策略均衡到不同的服務器上。下邊是一個簡單的示意圖:


用戶秒殺流量通過層層的負載均衡,均勻到了不同的服務器上,即使如此,集群中的單機所承受的 QPS 也是非常高的。如何將單機性能優化到極致呢?

通常訂票系統要處理生成訂單、減扣庫存、用戶支付這三個基本的階段。


系統要做的事情是要保證火車票訂單不超賣、不少賣,每張售賣的車票都必須支付才有效,還要保證系統承受極高的并發。


這三個階段的先后順序如何分配才合理?

當用戶并發請求到達服務端時,首先創建訂單,然后扣除庫存,等待用戶支付。

這種順序是一般人首先會想到的解決方案,這種情況下也能保證訂單不會超賣,因為創建訂單之后就會減庫存,這是一個原子操作。

會產生一些問題:
  • 在極限并發情況下,任何一個內存操作的細節都至關影響性能,尤其像創建訂單這種邏輯,一般都需要存儲到磁盤數據庫的,對數據庫的壓力是可想而知的。如果用戶存在惡意下單的情況,只下單不支付這樣庫存就會變少,會少賣很多訂單,雖然服務端可以限制 IP 和用戶的購買訂單數量,這也不算是一個好方法。


如果等待用戶支付了訂單在減庫存,第一感覺就是不會少賣。但是這是并發架構的大忌,因為在極限并發情況下,用戶可能會創建很多訂單。當庫存減為零的時候很多用戶發現搶到的訂單支付不了了,這也就是所謂的“超賣”。也不能避免并發操作數據庫磁盤 IO。


從上邊兩種方案的考慮,我們可以得出結論:只要創建訂單,就要頻繁操作數據庫 IO。
那么有沒有一種不需要直接操作數據庫 IO 的方案呢,這就是預扣庫存。先扣除了庫存,保證不超賣,然后異步生成用戶訂單,這樣響應給用戶的速度就會快很多;那么怎么保證不少賣呢?用戶拿到了訂單,不支付怎么辦?
我們都知道現在訂單都有有效期,比如說用戶五分鐘內不支付,訂單就失效了,訂單一旦失效,就會加入新的庫存,這也是現在很多網上零售企業保證商品不少賣采用的方案。
訂單的生成是異步的,一般都會放到 MQ、Kafka 這樣的即時消費隊列中處理,訂單量比較少的情況下,生成訂單非常快,用戶幾乎不用排隊。

扣庫存的藝術

從上面的分析可知,顯然預扣庫存的方案最合理。我們進一步分析扣庫存的細節,這里還有很大的優化空間,庫存存在哪里?怎樣保證高并發下,正確的扣庫存,還能快速的響應用戶請求?

在單機低并發情況下實現扣庫存通常:

為了保證扣庫存和生成訂單的原子性,需要采用事務處理,然后取庫存判斷、減庫存,最后提交事務,整個流程有很多 IO,對數據庫的操作又是阻塞的。這種方式根本不適合高并發的秒殺系統。接下來我們對單機扣庫存的方案做優化:本地扣庫存。我們把一定的庫存量分配到本地機器,直接在內存中減庫存,然后按照之前的邏輯異步創建訂單。
改進過之后的單機系統是這樣的:

這樣就避免了對數據庫頻繁的 IO 操作,只在內存中做運算,極大的提高了單機抗并發的能力。但是百萬的用戶請求量單機是無論如何也抗不住的,雖然 Nginx 處理網絡請求使用 Epoll 模型,c10k 的問題在業界早已得到了解決。但是 Linux 系統下,一切資源皆文件,網絡請求也是這樣,大量的文件描述符會使操作系統瞬間失去響應。上面我們提到了 Nginx 的加權均衡策略,不妨假設將 100W 的用戶請求量平均均衡到 100 臺服務器上,這樣單機所承受的并發量就小了很多。然后我們每臺機器本地庫存 100 張火車票,100 臺服務器上的總庫存還是 1 萬,這樣保證了庫存訂單不超賣,
以下是集群架構


問題接踵而至,在高并發情況下,現在還無法保證系統的高可用,假如這 100 臺服務器上有兩三臺機器因為扛不住并發的流量或者其他的原因宕機了。那么這些服務器上的訂單就賣不出去了,這就造成了訂單的少賣。要解決這個問題,我們需要對總訂單量做統一的管理,這就是接下來的容錯方案。服務器不僅要在本地減庫存,另外要遠程統一減庫存。有了遠程統一減庫存的操作,我們就可以根據機器負載情況,為每臺機器分配一些多余的“Buffer 庫存”用來防止機器中有機器宕機的情況。
結合下面架構圖具體分析:

采用 Redis 存儲統一庫存,因為 Redis 的性能非常高,號稱單機 QPS 能抗 10W 的并發。

在本地減庫存以后,如果本地有訂單,我們再去請求 Redis 遠程減庫存,本地減庫存和遠程減庫存都成功了,才返回給用戶搶票成功的提示,這樣也能有效的保證訂單不會超賣。當機器中有機器宕機時,因為每個機器上有預留的 Buffer 余票,所以宕機機器上的余票依然能夠在其他機器上得到彌補,保證了不少賣。Buffer 余票設置多少合適呢,理論上 Buffer 設置的越多,系統容忍宕機的機器數量就越多,但是 Buffer 設置的太大也會對 Redis 造成一定的影響。雖然 Redis 內存數據庫抗并發能力非常高,請求依然會走一次網絡 IO,其實搶票過程中對 Redis 的請求次數是本地庫存和 Buffer 庫存的總量。因為當本地庫存不足時,系統直接返回用戶“已售罄”的信息提示,就不會再走統一扣庫存的邏輯。這在一定程度上也避免了巨大的網絡請求量把 Redis 壓跨,所以 Buffer 值設置多少,需要架構師對系統的負載能力做認真的考量。

總結

秒殺系統是非常復雜的,本文僅簡單介紹模擬了一下單機如何優化到高性能,集群如何避免單點故障,保證訂單不超賣、不少賣的一些策略。還涉及完整的訂單系統還有訂單進度的查看,定時的從總庫存同步余票和庫存信息展示給用戶,以及用戶在訂單有效期內不支付,釋放訂單,補充到庫存等等。總之,負載均衡,分而治之,每臺機器處理好自己的請求,將自己的性能發揮到極致。合理的使用并發和異步,合理的壓榨 CPU,讓其發揮出應有的價值。

文章來源:https://juejin.im/post/5d84e21f6fb9a06ac8248149

如有侵權請聯系刪除


欄目分類
最近更新