解密 Redis 助力雙 11 背後電商秒殺系統

圖片

來源:t.cn/EAlQqQDjava

  • 背景
  • 秒殺的特徵
  • 秒殺系統

背景

秒殺活動是絕大部分電商選擇的低價促銷,推廣品牌的方式。既能夠給平臺帶來用戶量,還能夠提升平臺知名度。一個好的秒殺系統,能夠提升平臺系統的穩定性和公平性,得到更好的用戶體驗,提高平臺的口碑,從而提高秒殺活動的最大價值。redis

本文討論雲數據庫Redis版緩存設計高併發的秒殺系統。數據庫

秒殺的特徵

秒殺活動對稀少或特價的商品進行定時定量售賣,吸引成大量的消費者進行搶購,但又只有少部分消費者能夠下單成功。所以,秒殺活動將在必定時間內產生比平時大幾十倍倍,上百倍的頁面訪問流量和下單請求流量。瀏覽器

秒殺活動能夠分爲3個階段:緩存

  • 秒殺前:用戶不斷刷新商品詳情頁,頁面請求達到臨時開頭。
  • 秒殺開始:用戶點擊秒殺按鈕,下單請求達到暫時提早。
  • 秒殺後:一部分紅功下單的用戶不斷刷新訂單或產生退單操做,大部分用戶繼續刷新商品詳情頁等待退單機會。

消費者提交的訂單,通常作法是利用數據庫的行級鎖,只有搶到鎖的請求能夠進行庫存查詢和下單操做。可是在高併發的狀況下,數據庫沒法承受如此大的請求,每每須要整個服務被阻止,在消費者看來就是服務器停機機。服務器

秒殺系統

圖片

利用系統的層次結構,在每一個階段提早從新驗證,攔截無效流量,能夠減小大量無效的流量涌入數據庫。網絡

利用瀏覽器緩存和CDN抗壓靜態頁面流量數據結構

所以,咱們須要把秒殺商品詳情頁與普通的商品詳情頁分開。關於秒殺商品詳情頁試圖將能靜態化的元素靜態化處理,除了秒殺按鈕須要服務端進行動態判斷,其餘的靜態數據能夠緩存在瀏覽器和CDN上。這樣,秒殺前刷新頁面致使的流量進入服務端的流量只有很小的一部分。併發

利用識讀分離Redis緩存攔截流量app

CDN是第一級流量攔截,第二級流量攔截咱們使用支持讀寫分離的Redis。在這一階段咱們主要讀取數據,讀取分離Redis能支持高達60萬以上qps,徹底能夠支持需求。

首先經過數據控制模塊,提早將秒殺商品緩存到標識符分離Redis,並設置秒殺開始標記以下:

"goodsId_count"100 //總數
"goodsId_start"0   //開始標記
"goodsId_access"0  //接受下單數
  1. 秒殺開始前,服務從新讀取goodsId_Start爲0,直接返回未開始。

  2. 數據控制模塊將goodsId_start改成1,標誌秒殺開始。

  3. 服務最大化緩存開始標記位並開始接受請求,並記錄到redis中goodsId_access,商品剩餘數量爲(goodsId_count-goodsId_access)。

  4. 當接受下單數達到goodsId_count後,繼續攔截全部請求,商品剩餘數量爲0。

能夠拋光,最後成功參與下單的請求只有少部分能夠被接受。在高併發的狀況下,容許稍微多的流量進入。所以能夠控制接受下單數的比例。

利用主從版Redis緩存加速庫存扣量

成功避免下單後,進入下層服務,開始進行訂單信息校驗,庫存扣量。爲了不直接訪問數據庫,咱們使用主從版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腳本EVALSHA預先緩存在Redis,而後調用調用腳本,比直接調用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,便可知道這次請求是否扣量成功。

使用主從版Redis實現簡單的消息異步下單入庫

若是商品數量減小的時候,直接操做數據庫便可。若是秒殺的商品是1萬,甚至10萬等級,那數據庫鎖衝突將帶來很大的性能優點。。所以,利用消息組件,當秒殺服務將訂單信息寫入消息變量後,便可認爲下單完成,避免直接操做數據庫。

  1. 消息模塊組件依然可使用Redis實現,在R2中用列表數據結構表示。
```java
     orderList {
         [0] = {訂單內容}
         [1] = {訂單內容}
         [2] = {訂單內容}
         ...
     }
  1. 將訂單內容寫入
```java
    LPUSH orderList {訂單內容}
  1. 初步下單模塊從Redis中順序獲取訂單信息,將訂單寫入數據庫。
```java
     BRPOP orderList 0

經過使用Redis做爲消息收發器,異步處理訂單入庫,有效的提升了用戶的下單完成速度。

數據控制模塊管理秒殺數據同步

最開始,利用識別分離Redis進行流量限制,只讓部分流量進入下單。對於下單檢驗失敗和退單等狀況,須要讓更多的流量進來。所以,數據控制模塊須要定時將數據庫中的數據進行必定的計算,同步到主從版Redis,同時再同步到讀寫分離的Redis,讓更多的流量進來。

圖片

相關文章
相關標籤/搜索