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