秒殺活動是絕大部分電商選擇的低價促銷、推廣品牌的方式。不只能夠給平臺帶來用戶量,還能夠提升平臺知名度。一個好的秒殺系統,能夠提升平臺系統的穩定性和公平性,得到更好的用戶體驗,提高平臺的口碑,從而提高秒殺活動的最大價值。redis
本文討論如何利用 Redis 緩存設計高併發的秒殺系統。算法
秒殺活動對稀缺或者特價的商品進行定時定量售賣,吸引成大量的消費者進行搶購,但又只有少部分消費者能夠下單成功。所以,秒殺活動將在較短期內產生比平時大數十倍,上百倍的頁面訪問流量和下單請求流量。數據庫
秒殺活動能夠分爲3個階段:瀏覽器
一、秒殺前:用戶不斷刷新商品詳情頁,頁面請求達到瞬時峯值。 二、秒殺開始:用戶點擊秒殺按鈕,下單請求達到瞬時峯值。 三、秒殺後:一部分紅功下單的用戶不斷刷新訂單或者產生退單操做,大部分用戶繼續刷新商品詳情頁等待退單機會。緩存
消費者提交訂單,通常作法是利用數據庫的行級鎖,只有搶到鎖的請求能夠進行庫存查詢和下單操做。可是在高併發的狀況下,數據庫沒法承擔如此大的請求,每每會使整個服務 blocked,在消費者看來就是服務器宕機。安全
秒殺系統的流量雖然很高,可是實際有效流量是十分有限的。利用系統的層次結構,在每一個階段提早校驗,攔截無效流量,能夠減小大量無效的流量涌入數據庫。bash
秒殺前,用戶不斷刷新商品詳情頁,形成大量的頁面請求。因此,咱們須要把秒殺商品詳情頁與普通的商品詳情頁分開。對於秒殺商品詳情頁儘可能將能靜態化的元素靜態化處理,除了秒殺按鈕須要服務端進行動態判斷,其餘的靜態數據能夠緩存在瀏覽器和 CDN 上。這樣,秒殺前刷新頁面致使的流量進入服務端的流量只有很小的一部分。服務器
CDN 是第一級流量攔截,第二級流量攔截咱們使用支持讀寫分離的 Redis。在這一階段咱們主要讀取數據,讀寫分離 Redis 能支持高達60萬以上 qps,徹底能夠支持需求。網絡
首先經過數據控制模塊,提早將秒殺商品緩存到讀寫分離 Redis,並設置秒殺開始標記以下:數據結構
"goodsId_count": 100 //總數
"goodsId_start": 0 //開始標記
"goodsId_access": 0 //接受下單數
複製代碼
一、秒殺開始前,服務集羣讀取 goodsId_Start 爲 0,直接返回未開始。 二、數據控制模塊將 goodsId_start 改成1,標誌秒殺開始。 三、服務集羣緩存開始標記位並開始接受請求,並記錄到 redis 中 goodsId_access,商品剩餘數量爲(goodsId_count - goodsId_access)。 四、當接受下單數達到 goodsId_count 後,繼續攔截全部請求,商品剩餘數量爲 0。
能夠看出,最後成功參與下單的請求只有少部分能夠被接受。在高併發的狀況下,容許稍微多的流量進入。所以能夠控制接受下單數的比例。
成功參與下單後,進入下層服務,開始進行訂單信息校驗,庫存扣量。爲了不直接訪問數據庫,咱們使用主從版 Redis 來進行庫存扣量,主從版 Redis 提供10萬級別的 QPS。使用 Redis 來優化庫存查詢,提早攔截秒殺失敗的請求,將大大提升系統的總體吞吐量。
經過數據控制模塊提早將庫存存入 Redis,將每一個秒殺商品在 Redis 中用一個 hash 結構表示。
"goodsId" : {
"Total": 100
"Booked": 100
}
複製代碼
扣量時,服務器經過請求 Redis 獲取下單資格,經過如下 lua 腳本實現,因爲 Redis 是單線程模型,lua 能夠保證多個命令的原子性。
local n = tonumber(ARGV[1])
if not n or n == 0 then
return 0
end
local vals = redis.call("HMGET", KEYS[1], "Total", "Booked");
local total = tonumber(vals[1])
local blocked = tonumber(vals[2])
if not total or not blocked then
return 0
end
if blocked + n <= total then
redis.call("HINCRBY", KEYS[1], "Booked", n)
return n;
end
return 0
複製代碼
先使用SCRIPT LOAD
將 lua 腳本提早緩存在 Redis,而後調用EVALSHA
調用腳本,比直接調用EVAL
節省網絡帶寬:
redis 127.0.0.1:6379>SCRIPT LOAD "lua code"
"438dd755f3fe0d32771753eb57f075b18fed7716"
redis 127.0.0.1:6379>EVAL 438dd755f3fe0d32771753eb57f075b18fed7716 1 goodsId 1
複製代碼
秒殺服務經過判斷 Redis 是否返回搶購個數 n,便可知道這次請求是否扣量成功。
扣量完成後,須要進行訂單入庫。若是商品數量較少的時候,直接操做數據庫便可。若是秒殺的商品是1萬,甚至10萬級別,那數據庫鎖衝突將帶來很大的性能瓶頸。所以,利用消息隊列組件,當秒殺服務將訂單信息寫入消息隊列後,便可認爲下單完成,避免直接操做數據庫。
一、消息隊列組件依然可使用 Redis 實現,在 R2 中用 list 數據結構表示。
orderList {
[0] = {訂單內容}
[1] = {訂單內容}
[2] = {訂單內容}
...
}
複製代碼
二、將訂單內容寫入 Redis:
LPUSH orderList {訂單內容}
複製代碼
三、異步下單模塊從 Redis 中順序獲取訂單信息,並將訂單寫入數據庫。
BRPOP orderList 0
複製代碼
經過使用 Redis 做爲消息隊列,異步處理訂單入庫,有效的提升了用戶的下單完成速度。
最開始,利用讀寫分離 Redis 進行流量限制,只讓部分流量進入下單。對於下單檢驗失敗和退單等狀況,須要讓更多的流量進來。所以,數據控制模塊須要定時將數據庫中的數據進行必定的計算,同步到主從版 Redis,同時再同步到讀寫分離的 Redis,讓更多的流量進來。
做者:AlibabaCloud
出處:t.cn/EAlQqQD