Redis在秒殺功能的實踐

#Redis在資源秒殺場景中的使用git

業務概述

  • 秒殺資源:以周爲時長的資源。
  • 每一個頁面都會有秒殺資源,數量在1~8份,以隨機形式展現給訪客。
  • 每週秒殺資源價格由數據部門計算訂價,沒有有一個時間點進行搶購,如:每週三10點。購買者搶購數量能夠是 秒殺資源剩餘資源中的任意數量。
  • 購買者是否有搶購秒殺資源的權限,由用戶接口信息,帳戶信息,等權限接口等決定。
  • 購買者支付方式使用界面支付,系統生成購買者搶購支付加密信息,跳轉支付頁面,再支付界面後,異步回掉肯定是否購買成功,若是購買失敗須要及時退回秒殺資源庫存,以供他人報買。

業務流程

時間軸 業務 流程節點備註
週一 生成資源數據 流程①
週三10:00前 check資源數據 流程②
週三10:00 購買者秒殺秒殺資源 流程③
週三10:00後 購買者退款 流程④
週日 本週資源搶購結束,生成外網展現信息 流程⑤

Redis節點說明

  • 通用redis:用於SSO作統一登陸、以及非秒殺功能使用。
  • 緩存redis:用於存儲購買者熱身數據,搶購這查詢信息的緩存。
  • 核心redis:負責資源庫存剩餘數量,秒殺秒殺資源搶佔等核心業務實現,須要關閉redis的lru策略,程序控制內存中key的淘汰

Redis使用詳情

  • 緩存redis-數據熱身 流程①② (牛奶供給降級策略)github

    • 關鍵僞代碼redis

      cacheRedis.setex(key,EXPIRE_TIME_7D,info);
      複製代碼
    • 秒殺qps峯值在1w左右,可是超過60%的qps請求的是查詢列表方法,因此須要增長可購買秒殺資源緩存。spring

    • 關鍵僞代碼數據庫

      生成rediskey, objects包括ucid、用戶輸入入參、分頁信息等等
      public static String builder(String prefix, Object... objects) {
          String input = JSONObject.toJSONString(Arrays.asList(objects));
          String output = Util.md5_16(input);
          return prefix+output;
      }
      cacheRedis.setex(key,EXPIRE_TIME_2S,info);
      複製代碼
    • 設計優勢:借鑑spring-data-redis將入參通用爲objects...序列化,而後將JsonString Md5壓縮爲16位,這裏主要因爲在秒殺開始時,redis數據會出現大量緩存列表數據,redis儲存100w個value長度爲32位,key長度爲16位的數據時,須要使用個130MB內存,若是key的長度爲32位時須要160MB左右的內存,因此壓縮key的長度在這種場景頗有必要。緩存

  • 核心redis-秒殺資源秒殺 流程③bash

    • 每一個秒殺資源擁有本身的隊列,完成多隊列,低隊列長度的秒殺。服務器

    • 關鍵僞代碼架構

      String key = PURCHASING_PRODUCT + productId;
      Long count = coreRedis.llen(key);
      判斷count是否大於庫存
      判斷count+用戶欲購買秒殺資源數量(share)是否大於庫存
      
      String[] values = (uuid+uid) * share; 
      
      if (inventory - coreRedis.lpush(key, values)) < 0) {
          coreRedis.lrem(key, share, values);
      }
      
      例如:id:1 秒殺資源有3份流量的庫存, 
      當llen時發現秒殺資源在redis中沒有數據,
      購買者20xxxxx1想買此資源3份流量,
      這時lpush後發現超賣,lrem退回庫存。
      redis 127.0.0.1:6379> lrange XX_PRODUCT_1 0 -1
      1) "jali7xz20xxxxx1"
      2) "3whsh6b20xxxxx2"
      3) "3whsh6b20xxxxx2"
      4) "3whsh6b20xxxxx2"
      複製代碼
    • 設計優勢:核心命令llen、lpush的時間複雜度都是O(1)、lrem時間複雜度是O(N),官方lrem給出的複雜度是O(N)但我以爲在這種使用場景下lrem的複雜度應該無極限接近於O(count),可是將補償操做封裝爲原子性,且支持屢次、冪等執行。曾經也想過用一些getset,setnx,pipelin、將庫存緩存到隊列而後pop、事務等實現秒殺。可是性能、或者魯棒性在這種場景下都沒有以上設計表現出色,並且這種方式在支付失敗,或者查詢到未支付的狀況下馬上冪等lrem秒殺資源隊列的訂單,其餘有資格購買的購買者能夠繼續購買。異步

Redis線上使用狀況

  • 緩存redis (圖片來源地址:github)

cache redis

  • 核心redis

cache redis

Redis使用總結

  • 使用一主一從,rdb爲備份策略的redis架構,QPS在8W如下是沒有任何問題的(第一期秒殺資源秒殺,在沒有作redis多庫負載切分,以及沒有優化使用的狀況下到了5W的QPS,沒有出現超時連接,或者獲取不到鏈接池資源的狀況,也和沒有使用事務以及採用的低複雜度命令實現有關
  • 像列表頁緩存,切勿爲了減小redis的開銷,將數據庫每一列放到redis中,在redis中查詢彙總,例如:每一個秒殺資源都放在redis中,秒殺資源頁須要10次redis連接才能完成一次列表頁的組裝。這樣作會將服務器的qps成幾何倍數的擴大到與redis的qps中形成系統獲取不到redis鏈接資源
  • 若是redis只用做緩存數據,且追求極限性能,master能夠關閉內存快照和日誌記錄,有slave節點完成。
相關文章
相關標籤/搜索