秒殺
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向使用者傳送一個秒殺成功通知。前端以此來判斷是否秒殺成功,秒殺成功則進入秒殺訂單詳情,否則秒殺失敗。