秒殺中的常見問題

logan發表於2021-10-02

秒殺

1.限流

固定視窗演算法:時間節點無法動態更新,即 set key expire 10s(1-11秒,2-12秒)處理不好

滑動視窗演算法: zset member id score 10 解決時間節點問題 score即時間戳,可以動態獲取(currentTime - score)時間內的總訪問量,藉此判斷是否達標

漏桶演算法:redis中好像有類似實現

令牌桶演算法:Guava RateLimiter

2.降級(也叫做熔斷)

spring cloud sentinel

3.快取

3.1快取一致性問題

A. 先更新Db,再delete redis

兩個問題:

a .更新Db成功,刪除快取失敗。導致redis中有髒資料(可以藉助rocketmq不斷重試解決)

b.1)快取剛好失效

(2)請求A查詢資料庫,得一箇舊值

(3)請求B將新值寫入資料庫

(4)請求B刪除快取

(5)請求A將查到的舊值寫入快取(根據耗時情況可以推算出概率比較低)

B 先刪除快取,再更新Db

​ (1)A執行緒刪除快取,寫db更新為data2

​ (2)b執行緒查詢發現快取不存在

​ (3)b執行緒同步當前d b最新資料da ta1到redis

​ (4)a執行緒將資料data2寫入DB .髒資料發生

3.2 解決方案

A.延時雙刪

先刪除快取,再更新DB,再非同步刪除快取(通過rocketmq非同步重試機制,確保刪除成功)

B.訂閱binLog機制

阿里開源的canal

3.3應用 秒殺實現

限流:zset滑動視窗限流

降級:spring cloud sentinel+open Feign熔斷

快取:

延時雙刪策略

1.庫存扣減問題

提交訂單時

付款時

提交訂單時預先扣減庫存,超時回覆:定時任務輪訓資料庫沒有付款的訂單,超時訂單歸還庫存

2.熱點資料快取

延時雙刪策略

3.庫存防止超賣問題

樂觀鎖機制 update

update stock
   sale = sale + 1,
   version = version + 1,
  WHERE id = #{id,jdbcType=INTEGER}
  AND version = #{version,jdbcType=INTEGER}

或者
 
  update stock
   sale = sale + 1
  WHERE id = id
  AND sale={#sale}

悲觀鎖機制

在Service層給更新表新增一個事務,這樣每個執行緒更新請求的時候都會先去鎖表的這一行(悲觀鎖),更新完庫存後再釋放鎖。可這樣就太慢了,1000個執行緒可等不及。

beginTranse(開啟事務)

try{

    //quantity為請求減掉的庫存數量
    $dbca->query('update s_store set amount = amount - quantity where amount>=quantity and postID = 12345');

}catch($e Exception){

    rollBack(回滾)

}

commit(提交事務)

具體方案

  • 在系統初始化時,將商品的庫存數量載入到Redis快取中;
  • 接收到秒殺請求時,在Redis中進行預減庫存,當Redis中的庫存不足時(庫存<0可以在記憶體中設定一個標誌量boolean=已經賣光,就不需要再去請求redis增加網路開銷了,分散式情況下通過zookeeper獲得相應通知),直接返回秒殺失敗,否則繼續進行第3步;
  • 將請求放入非同步佇列中,返回正在排隊中;
  • 服務端非同步佇列將請求出隊,出隊成功的請求可以生成秒殺訂單,減少資料庫庫存(失敗就會回滾,並且會恢復redis中的庫存資料即加1),返回秒殺訂單詳情。
  • 當後臺訂單建立成功之後可以通過websocket向使用者傳送一個秒殺成功通知。前端以此來判斷是否秒殺成功,秒殺成功則進入秒殺訂單詳情,否則秒殺失敗。