電商秒殺系統設計

wuzhenhuai發表於2019-05-09

本章主要介紹高併發業務(秒殺活動)系統是如何設計的。

設計針對於該虛擬命題:有10000件商品,每個使用者最多購買2件。五分鐘未付款直接退單,可手動退單。

在看設計方案時我們先來整理下秒殺活動會帶來什麼樣的高併發

  1. 秒殺開始前:可能會有大量請求商品詳情頁。
  2. 秒殺進行時:大量請求下單。

請求流控

第一層 :設定瀏覽器快取與CDN

商品詳情頁相關介面設定瀏覽器快取。相關圖片,CSS,JS一些靜態資源儲存CDN。

瀏覽器快取的設計方案:設定1分鐘的強制快取,然後通過協商快取你判斷該快取是否有效。(既能限流也能即使更新頁面最新情況)

瀏覽器快取:當你請求HTTP請求後收到一個HTTP響應體的時候,瀏覽器會判斷響應頭中是否有快取的標識,如果有,則會把請求內容存入硬碟中(或者記憶體中),下次的準備發起相同請求時瀏覽器會自行判斷快取內容是否有效,如果有效則不進行請求,直接獲取快取內容。

CDN:把一些靜態資源交由第三方平臺儲存。這樣請求時無需訪問自己的伺服器並且響應速度也比較快。

第二層 :設定介面請求頻率

該設定主要是過濾一些非常規可能請求(模擬請求)。

  1. 前端JS控制下單按鈕不能重複點選。
  2. 通過nginx伺服器限流配置使同一IP頻率限制,如果超過限制則返回500狀態碼。

    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; 
    location /login/ {
      limit_req zone=mylimit burst=20 nodelay;
      limit_req_status 444;
    }
    // limit_req_zone 限流的配置
    // $binary_remote_addr 為用ip地址, zone=mylimit:10m 開闢一個10M的名為mylimit的空間 rate=10r/s 每秒10請求
    // limit_req //使用限流配置
    // burst=20 //接受20個快取請求, nodelay 不延遲執行

提示業務響應速度

通過請求流程可以過濾無效請求,但是如果使用者體量太大,通過流控仍有大量請求。

此時我們就需要加快程式的處理速度進而提升請求的響應速度,響應速度提升後單位時間內處理的請求就變多了。

讀優化

通過redis快取秒殺過程中會用到的資料。

寫優化

解耦相關的入庫操作通過訊息佇列中介軟體RabbitMQ

邏輯流程優化

及時響應無效的請求(越容易判斷寫在越前面)

具體實現流程

下單介面邏輯流程

//redis 相關資料的格式定義
'is_start' => 0  //  0 未開始,1開始, 2結束
'buy:'.$userID.':'.$goodID => 0 // userID使用者已購買goodID商品的數量
'stock'.$goodID => {
    'stock' => 10000 // 商品庫存量
    'sales' => 0 //已售量
}
'order':$userID:$order_no => {
     訂單資訊
}
  • 判斷秒殺活動是否開啟
  • 判斷使用者是否還能購買 checkBuy 指令碼返回0表示不能購買
  • 判斷庫存是否滿足 checkStock 指令碼返回0表示無庫存,無庫存的時候要把使用者已購買數量新增回去
  • 將訂單請求放入下單佇列,並且響應訂單號給前端。 訊息佇列內容 使用者id, 商品id,商品數量,訂單號。
  • 非同步消費下單佇列,寫入資料庫,並且寫入redis 'order':$order_on。
    • 如果成功redis儲存訂單資訊,並且新增過期佇列延遲5分鐘。過期佇列內容 使用者id, 商品id,商品數量,訂單號。
    • 如果失敗儲存失敗原因(釋放庫存 'stock'.$goodID,釋放已購買數 'buy:'.$userID.':'.$goodID )。
      *非同步消費過期佇列,如果已過期則更改訂單狀態,釋放庫存 'stock'.$goodID,釋放已購買數 'buy:'.$userID.':'.$goodID

獲取訂單介面邏輯流程

  • 通過請求的order_no與使用者身份標識去redis 查詢 'order':$userID:$order_no資料並返回。

取消訂單與退單介面

  • 更改訂單狀態,釋放庫存 'stock'.$goodID,釋放已購買數 'buy:'.$userID.':'.$goodID

前端接收下單響應結果處理

  • 如果響應狀態嗎!200, 提示搶購失敗

  • 如果是200響應,下單介面會返回order_no。前端可以展示訂單建立中。

  • 然後ajax去輪詢獲取訂單介面,請求引數order_no.獲取redis資訊。如果成功則展示結算頁。如果失敗則展示失敗原因。

結算頁

  • 支付選擇收貨地址並且進行支付。

訂單頁

  • 可以進行取消訂單,退單
//checkBuy指令碼
    script load "lua code"
    //lua code
    local n =  tonumber(ARGV[1]);
    if not n  or n == 0 then
        return 0       
    end    
    local val = tonumber(redis.call('GET', KEYS[1]));        
    if  (not val) or (val + n <= 2) then
        redis.call('INCRBY', KEYS[1], n);                 
        return n;   
    end                
    return 0;
    //49d185704d033c30e29b09a72e687d4c322e7801
    evalsha 49d185704d033c30e29b09a72e687d4c322e7801 1 'buy:'.$userID.':'.$goodID 1
//checkStock指令碼
    script load "lua code"
    //lua code
    local n = tonumber(ARGV[1])
    if not n  or n == 0 then
        return 0       
    end                
    local vals = redis.call('HMGET', KEYS[1], 'stock', 'sales');
    local stock = tonumber(vals[1])
    local sales = tonumber(vals[2])
    if not stock or not sales then
        return 0       
    end                
    if sales + n <= stock then
        redis.call('HINCRBY', KEYS[1], 'sales', n)                                   
        return n;   
    end                
    return 0
    //51046114c9b5b102554a381969343493e29dcb34
    evalsha 51046114c9b5b102554a381969343493e29dcb34 1 'stock'.$goodID 1

相關文章