當前位置:首頁 > 公眾號精選 > 小林coding
[導讀]雙十一又要到了,我有點慌,以前一個人的時候,一分錢都不花,現(xiàn)在有了女票,不僅得剁手,還得幫忙搶各種秒殺商品。今年,我真的不想再去搶秒殺了,為什么呢?太難了,成千上萬的人就盯著秒殺放出來的那點商品。我憑著單身十幾年的手速也搶不過啊。我苦思妙想,終于想出一條完(zuo)美(si)妙計...



雙十一又要到了,我有點慌,以前一個人的時候,一分錢都不花,現(xiàn)在有了女票,不僅得剁手,還得幫忙搶各種秒殺商品。


今年,我真的不想再去搶秒殺了,為什么呢?
太難了,成千上萬的人就盯著秒殺放出來的那點商品。我憑著單身十幾年的手速也搶不過啊。
我苦思妙想,終于想出一條完(zuo)美(si)妙計:給女朋友講講程序員是如何做一個秒殺系統(tǒng)的。
對頭,就是要用知識的海洋淹沒她。如果她不愿意聽,或者聽不懂,那么今年就不參加雙十一了。
至于拒絕理由嘛。。。那就是【你都不認真聽我說話,你一定是不愛我了】;如果不幸她聽懂了,也不礙事,至少讓她知道了我們程序員兄弟多么牛(jian)逼(xin)。
于是,我找到了女朋友阿醬。
:吶,你知道我工作上也經(jīng)常做秒殺系統(tǒng)嗎?今天我就給你講講秒殺是怎么做的,如果你聽懂了,今年我就幫你搶秒殺!
:可是要是我聽不懂怎么辦?。?/span>
:我的寶貝怎么可能聽不懂,要是聽不懂一定是我講得不夠好!
:那。。。我試試吧

問題拋出首先,秒殺有哪些要考慮的地方呢?


第一點,海量請求,服務要能扛住。
秒殺活動一開始,瞬間會有海量流量涌入,熱門的商品甚至會有幾百萬人來搶。這個規(guī)模的流量砸下來,服務可能就掛了,活動也就GG了,收獲的只有罵聲。
怎么讓服務能打能抗,是需要考慮的問題。


第二點,不能超賣。
因為秒殺有時候就是賠本賺吆喝,價格可能比成本價還低。而這時候要是比原計劃的數(shù)量賣多了,那到底發(fā)不發(fā)貨呢?
發(fā)貨會超預算虧損,要是超賣數(shù)量過多,說不定廠子都要倒閉了;不發(fā)貨會被投訴,影響商家聲譽。
不管怎樣,都是硬傷,只能找程序員賠錢了。

第三點,盡量避免少賣。
少賣會比超賣好一些,商家不存在經(jīng)濟上的損失。但要是被眼尖的消費者發(fā)現(xiàn)的話,也是免不了一場麻煩的。所以我們還是要盡可能避免這種情況。
第四點,保證觸達到用戶而不是黃牛。
黃牛可能是開腳本,一次發(fā)很多請求過來,搶到之后再轉賣。但我們做活動,希望的就是回饋客戶,進而吸引用戶,而不是去讓黃牛賺外快。因此,我們要盡量擋住黃牛的魔爪。
不聽了,不聽了,腦殼痛。
那今年不用剁手啦~
:???你繼續(xù),我能行!
:問題我說完了,下面才是重點,來說說解決方案。
:我好像已經(jīng)開始聽不懂了。。。



對癥下藥

硬抗高并發(fā)


在高并發(fā)的情況下,MySQL就顯得有些力不從心了。
一方面是MySQL本身要支持事務的ACID,單機性能不高。
另一方面,MySQL是個單機數(shù)據(jù)庫,本身是不能水平擴展的,如果要搞分庫分表,費時費力。
這時候就可以借助MySQL的好伙伴Redis的能力。
Redis小哥可是單機支撐每秒幾萬的寫入,并且可以做成集群,提高擴展能力的。
我們可以先將庫存名額預加載到Redis,然后在Redis中進行扣減,扣減成功的再通過消息隊列,傳遞到MySQL做真正的訂單生成。
為什么要通過消息隊列呢?
主要有兩點好處,一個是這種投遞的方式,可以讓搶和購解耦。另一個是可以很方便地限頻,不至于讓MySQL過度承壓。


我們說回Redis,如果請求量超過6W每秒,就要考慮使用多個Redis來分流。預計有100W請求量,我們就可以臨時調度20個Redis實例來支持,一個5W/s,留點Buffer。
這種模式倒是不需要使用Redis Cluster那種一致性Hash的做法,直接前面接個Nginx,做負載均衡就可以了。


拒絕超賣


解決了高并發(fā)的問題,我們再來看看怎么防止超賣。
既然我們將庫存名額加載到了Redis,那就需要精確計數(shù)。
我們搶購場景最核心的,有兩個步驟:
第一步,判斷庫存名額是否充足;
第二步,減少庫存名額,扣減成功就是搶到。


這里有一個問題要考慮,如果第一步判斷的時候還有庫存,但是由于是并發(fā)操作,實際調用的時候,可能已經(jīng)沒有庫存了,這樣就會造成超賣。
所以第一步和第二步都是需要原子操作的。
但是Redis沒有直接提供這種場景原子化的操作。
遇事不要慌,仔細想一想,Redis是不是還有個特性,專門整合原子操作,對,就是它——Lua。

Redis?Lua,可以說是專門為解決原子問題而生,在Lua腳本中調用Redis的多個命令,這些命令整體上會作為原子操作來進行。

盡量避免少賣


少賣什么情況會出現(xiàn)呢?
庫存減少了,但用戶訂單沒生成。
什么情況會這樣呢?
在Redis操作成功,但是向Kafka發(fā)送消息失敗,這種情況就會白白消耗Redis中的庫存。


作為一個專業(yè)的程序員,只要知道問題是什么、怎么發(fā)生的,問題就解決了一半。說白了,我們只需要保證Redis庫存 Kafka消耗的最終一致性。
但是一致性問題,一直是分布式場景的惡龍,要對付并不容易。
第一種,也最簡單的方式,在投遞Kafka失敗的情況下,增加漸進式重試;
第二種,更安全一點,就是在第一種的基礎上,將這條消息記錄在磁盤上,慢慢重試;
第三種,寫磁盤之前就可能失敗,可以考慮走WAL路線,但是這樣做下去說不定就做成MySQL的undo log,redo log這種WAL技術了,會相當復雜,沒有必要。

針對少賣這種極端場景可接受的問題,一般選擇第二種方式即可,畢竟是異常情況的小概率事件,真出問題了大不了人工介入。

打擊黃牛


黃牛的惡劣影響,很多時候是被低估了。
不僅僅是侵害了正常用戶的權益,同時由于黃牛善于使用腳本,很容易造成大量的惡意請求,讓本就不富裕的服務器資源,雪上加霜。
通常來說,為了打擊黃牛,最常見的方式是限購,一個用戶最多只能搶到N份,這樣可以大大保障正常用戶的權益。
具體怎么做呢,為了性能,我們還是將限制邏輯加入到Redis中,所以我們的Lua腳本中,第一步查詢庫存,第二步扣減庫存,需要優(yōu)化為第一步查詢庫存,第二步查詢用戶已購買個數(shù),第三步扣減庫存,第四步記錄用戶購買數(shù)


這里需要注意的是,如果使用Redis集群,那么Redis的一致性Hash Key,需要根據(jù)用戶來分Key,不然用戶數(shù)據(jù)會查詢不到。
有了限購,我們可以保證貨品不會被黃牛占據(jù)太多,那么還剩一個問題,黃牛大多是通過代碼來搶購,點擊速度比人點擊快得多,這樣就導致了競爭不公平。
作為追求極致的coder,我們希望還能更進一步,做到競爭公平。
怎么解決呢?某個用戶請求接口次數(shù)過于頻繁,一般說明是用腳本在跑,可以只針對該用戶做限制。
針對IP做限制也是常見做的做法,但這樣容易誤殺,主要考慮到使用同一個網(wǎng)絡的用戶,可能都是一個出口IP。限制IP,會導致正常用戶也受到影響。

更好用的方案是加上一個驗證碼驗證。驗證碼符合91原則,90%的時間,都用在驗證碼輸入上,所以使用腳本點擊的影響會降到很低。


當然,我們要明白沒有銀彈,這種方式缺點在于降低了用戶的體驗感。

故事尾聲:這樣一來,我們的秒殺場景就基本OK啦!

:怎么樣,聽懂了嗎?
:嗯嗯!(心虛的用力點頭)我們能去秒殺了嗎?
:那我來檢驗一下?
:我難道不是你的寶貝了嗎?
無奈地嘆氣:說吧,你這次又想搶什么東西?
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉
關閉