2021-01-29 分類: 網站建設
每到節(jié)假日期間,一二線城市返鄉(xiāng)、外出游玩的人們幾乎都面臨著一個問題:搶火車票!
從上邊兩種方案的考慮,我們可以得出結論:只要創(chuàng)建訂單,就要頻繁操作數據庫 IO。
那么有沒有一種不需要直接操作數據庫 IO 的方案呢,這就是預扣庫存。先扣除了庫存,保證不超賣,然后異步生成用戶訂單,這樣響應給用戶的速度就會快很多;那么怎么保證不少賣呢?用戶拿到了訂單,不支付怎么辦?
我們都知道現在訂單都有有效期,比如說用戶五分鐘內不支付,訂單就失效了,訂單一旦失效,就會加入新的庫存,這也是現在很多網上零售企業(yè)保證商品不少賣采用的方案。
訂單的生成是異步的,一般都會放到 MQ、Kafka 這樣的即時消費隊列中處理,訂單量比較少的情況下,生成訂單非常快,用戶幾乎不用排隊。
扣庫存的藝術
從上面的分析可知,顯然預扣庫存的方案最合理。我們進一步分析扣庫存的細節(jié),這里還有很大的優(yōu)化
為了保證扣庫存和生成訂單的原子性,需要采用事務處理,然后取庫存判斷、減庫存,最后提交事務,整個流程有很多 IO,對數據庫的操作又是阻塞的。
這種方式根本不適合高并發(fā)的秒殺系統(tǒng)。接下來我們對單機扣庫存的方案做優(yōu)化:本地扣庫存。
我們把一定的庫存量分配到本地機器,直接在內存中減庫存,然后按照之前的邏輯異步創(chuàng)建訂單。
改進過之后的單機系統(tǒng)是這樣的:
這樣就避免了對數據庫頻繁的 IO 操作,只在內存中做運算,極大的提高了單機抗并發(fā)的能力。
但是百萬的用戶請求量單機是無論如何也抗不住的,雖然 Nginx 處理網絡請求使用 Epoll 模型,c10k 的問題在業(yè)界早已得到了解決。
但是 Linux 系統(tǒng)下,一切資源皆文件,網絡請求也是這樣,大量的文件描述符會使操作系統(tǒng)瞬間失去響應。
上面我們提到了 Nginx 的加權均衡策略,我們不妨假設將 100W 的用戶請求量平均均衡到 100 臺服務器上,這樣單機所承受的并發(fā)量就小了很多。
然后我們每臺機器本地庫存 100 張火車票,100 臺服務器上的總庫存還是 1 萬,這樣保證了庫存訂單不超賣,下面是我們描述的集群架構:
問題接踵而至,在高并發(fā)情況下,現在我們還無法保證系統(tǒng)的高可用,假如這 100 臺服務器上有兩三臺機器因為扛不住并發(fā)的流量或者其他的原因宕機了。那么這些服務器上的訂單就賣不出去了,這就造成了訂單的少賣。
要解決這個問題,我們需要對總訂單量做統(tǒng)一的管理,這就是接下來的容錯方案。服務器不僅要在本地減庫存,另外要遠程統(tǒng)一減庫存。
有了遠程統(tǒng)一減庫存的操作,我們就可以根據機器負載情況,為每臺機器分配一些多余的“Buffer 庫存”用來防止機器中有機器宕機的情況。
我們結合下面架構圖具體分析一下:
我們采用 Redis 存儲統(tǒng)一庫存,因為 Redis 的性能非常高,號稱單機 QPS 能抗 10W 的并發(fā)。
在本地減庫存以后,如果本地有訂單,我們再去請求 Redis 遠程減庫存,本地減庫存和遠程減庫存都成功了,才返回給用戶搶票成功的提示,這樣也能有效的保證訂單不會超賣。
當機器中有機器宕機時,因為每個機器上有預留的 Buffer 余票,所以宕機機器上的余票依然能夠在其他機器上得到彌補,保證了不少賣。
Buffer 余票設置多少合適呢,理論上 Buffer 設置的越多,系統(tǒng)容忍宕機的機器數量就越多,但是 Buffer 設置的太大也會對 Redis 造成一定的影響。
雖然 Redis 內存數據庫抗并發(fā)能力非常高,請求依然會走一次網絡 IO,其實搶票過程中對 Redis 的請求次數是本地庫存和 Buffer 庫存的總量。
因為當本地庫存不足時,系統(tǒng)直接返回用戶“已售罄”的信息提示,就不會再走統(tǒng)一扣庫存的邏輯。
這在一定程度上也避免了巨大的網絡請求量把 Redis 壓跨,所以 Buffer 值設置多少,需要架構師對系統(tǒng)的負載能力做認真的考量。
代碼演示
Go 語言原生為并發(fā)設計,我采用 Go 語言給大家演示一下單機搶票的具體流程。
初始化工作
Go 包中的 Init 函數先于 Main 函數執(zhí)行,在這個階段主要做一些準備性工作。
我們系統(tǒng)需要做的準備工作有:初始化本地庫存、初始化遠程 Redis 存儲統(tǒng)一庫存的 Hash 鍵值、初始化 Redis 連接池。
另外還需要初始化一個大小為 1 的 Int 類型 Chan,目的是實現分布式鎖的功能。
也可以直接使用讀寫鎖或者使用 Redis 等其他的方式避免資源競爭,但使用 Channel 更加高效,這就是 Go 語言的哲學:不要通過共享內存來通信,而要通過通信來共享內存。
Redis 庫使用的是 Redigo,下面是代碼實現:
- ...
- //localSpike包結構體定義
- package localSpike
- type LocalSpike struct {
- LocalInStock int64
- LocalSalesVolume int64
- }
- ...
- //remoteSpike對hash結構的定義和redis連接池
- package remoteSpike
- //遠程訂單存儲健值
- type RemoteSpikeKeys struct {
- SpikeOrderHashKey string //redis中秒殺訂單hash結構key
- TotalInventoryKey string //hash結構中總訂單庫存key
- QuantityOfOrderKey string //hash結構中已有訂單數量key
- }
- //初始化redis連接池
- func NewPool() *redis.Pool {
- return &redis.Pool{
- MaxIdle: 10000,
- MaxActive: 12000, // max number of connections
- Dial: func() (redis.Conn, error) {
- c, err := redis.Dial("tcp", ":6379")
- if err != nil {
- panic(err.Error())
- }
- return c, err
- },
- }
- }
- ...
- func init() {
- localSpike = localSpike2.LocalSpike{
- LocalInStock: 150,
- LocalSalesVolume: 0,
- }
- remoteSpike = remoteSpike2.RemoteSpikeKeys{
- SpikeOrderHashKey: "ticket_hash_key",
- TotalInventoryKey: "ticket_total_nums",
- QuantityOfOrderKey: "ticket_sold_nums",
- }
- redisPool = remoteSpike2.NewPool()
- done = make(chan int, 1)
- done <- 1
- }
“12306”是如何支撐百萬QPS的?
瀏覽地址:http://m.rwnh.cn/news17/98017.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供服務器托管、手機網站建設、建站公司、營銷型網站建設、網站維護、品牌網站建設
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內容