日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

淺談Redis在秒殺場景的作用_Redis

作者:JavaEdge. ? 更新時間: 2023-03-20 編程語言

秒殺業務特點:限時限量,業務系統要處理瞬時高并發請求,Redis是必需品。

秒殺可分成秒殺前、秒殺中和秒殺后三階段,每個階段的請求處理需求不同,Redis具體在秒殺場景的哪個環節起到作用呢?

1 秒殺負載特征

秒殺商品的庫存量<<購買該商品的用戶數,且會限定用戶只能在一定時間段內購買。
這給秒殺系統帶來兩個明顯負載特征:

1.1 瞬時并發訪問量很高

一般DB每秒只能支撐k級并發,而Redis并發能達到w級。所以,當大量并發請求涌入秒殺系統時,要使用Redis先攔截大部分請求,避免大量請求直接發給DB

1.2 讀多寫少

讀還是簡單的查詢操作。秒殺下,用戶需先查驗商品是否還有庫存(即根據商品ID查詢該庫存量),只有庫存有余量時,秒殺系統才能進行庫存扣減、下單??杀镜鼐彺姹4鎺齑媸欠駷?0 的標識,避免再請求 redis。

庫存查驗操作是典型KV查詢,Redis正滿足。但秒殺只有小部分用戶能成功下單,所以:
商品庫存查詢操作(讀操作)>>庫存扣減、下單操作(寫操作)

一般把秒殺活動分成三個階段:

2 秒殺階段

2.1 秒殺前

用戶不斷刷新商品詳情頁,導致詳情頁瞬時請求量猛增。

一般盡量靜態化商品詳情頁的頁面元素,然后使用CDN或瀏覽器緩存這些靜態化元素。
秒殺前的大量請求可直接由CDN或瀏覽器緩存服務,不會到達服務端。

2.2 秒殺中

大量用戶點擊商品詳情頁上的秒殺按鈕,會產生大量的并發請求查詢庫存。一旦某個請求查詢到有庫存,緊接著系統就會進行庫存扣減。然后,系統會生成實際訂單,并進行后續處理,例如訂單支付和物流服務。如果請求查不到庫存,就會返回。用戶通常會繼續點擊秒殺按鈕,繼續查詢庫存。

該階段主要操作:

  • 庫存查驗
  • 庫存扣減
  • 訂單處理

每個秒殺請求都會查詢庫存,而請求只有查到有庫存余量,后續的庫存扣減和訂單處理才會被執行。所以,該階段最大并發壓力在庫存查驗。就需使用Redis保存庫存量,請求直接從Redis讀庫存并查驗。

庫存扣減和訂單處理是否都可交給后端DB執行?

訂單處理可在DB執行,但庫存扣減操作,不能交給DB。

為何非在DB處理訂單呢?

訂單處理涉及支付、商品出庫、物流等多個關聯操作,這些操作本身涉及DB中的多張表,要保證事務性,需在DB完成。
訂單處理時,請求壓力已不大,DB完全可支撐。

為啥庫存扣減操作不能在DB執行

一旦請求查到有庫存,即發送該請求的用戶獲得商品購買資格,用戶就會下單了。同時,商品庫存余量也需-1。

若把庫存扣減的操作放到DB,會帶來風險:

額外開銷
Redis保存庫存量,而庫存量最新值又是DB在維護,所以DB更新后,還要和Redis進行同步,這增加額外操作邏輯

下單量>實際庫存量,超賣!
由于DB處理性能較慢,無法及時更新庫存余量,可能導致大量庫存查驗請求讀到舊庫存值,并下單。就會出現下單數量>實際庫存量,導致超賣

所以,要在Redis進行庫存扣減:

  • 當庫存查驗完成后,一旦庫存有余量,立即在Redis扣庫存
  • 為避免請求查詢到舊庫存值,庫存查驗、庫存扣減兩個操作需保證原子性

秒殺中需要Redis參與的兩個環節:

2.3 秒殺結束后

該階段,可能還有部分用戶刷新商品詳情頁,嘗試等待有其他用戶退單。而已成功下單的用戶會刷新訂單詳情,跟蹤訂單進度。
不過,此階段的用戶請求量已下降很多,服務器端一般都能支撐。

3 Redis可支撐秒殺的特性

3.1 支持高并發

Redis先天支持。且若有多個秒殺商品,也可使用切片集群,用不同實例保存不同商品的庫存,避免使用單實例導致所有秒殺請求都集中在一個實例。
使用切片集群時,先CRC計算不同秒殺商品K對應Slot,然后在分配Slot和實例對應關系時,才能把不同秒殺商品對應的Slot分配到不同實例保存。

3.2 保證庫存查驗和庫存扣減的原子性

使用Redis的原子操作或分布式鎖。

4 基于原子操作支撐秒殺

秒殺中的一個商品的庫存對應兩個信息:

  • 總庫存量
  • 已秒殺量

這種數據模型正好一個key(商品ID)對應兩個屬性(總庫存量和已秒殺量),可用Hash保存:

key: itemID
value: {total: N, ordered: M}

itemID 商品編號total,總庫存量ordered,已秒殺量

因為庫存查驗、庫存扣減這兩個操作要保證一起執行,一個直接的方法就是使用Redis的原子操作。

庫存查驗、庫存扣減是兩個操作,需Lua腳本保證原子執行:

#獲取商品庫存信息            
local counts = redis.call("HMGET", KEYS[1], "total", "ordered");
#將總庫存轉換為數值
local total = tonumber(counts[1])
#將已被秒殺的庫存轉換為數值
local ordered = tonumber(counts[2])  
#如果當前請求的庫存量加上已被秒殺的庫存量仍然小于總庫存量,就可以更新庫存         
if ordered + k <= total then
    #更新已秒殺的庫存量
    redis.call("HINCRBY",KEYS[1],"ordered",k)                              return k;  
end               
return 0

然后就能在Redis客戶端,使用EVAL命令執行腳本??蛻舳烁鶕_本返回值,確定秒殺是否成功:

  • 返回值k,成功
  • 0,失敗

5 基于分布式鎖支撐秒殺

讓客戶端向Redis申請分布式鎖,拿到鎖的客戶端才能執行庫存查驗、庫存扣減。
這樣大量秒殺請求就會在爭奪分布式鎖時被過濾掉。
庫存查驗、扣減也不用原子操作了,因為多個并發客戶端只有一個客戶端能夠拿到鎖,已保證客戶端并發訪問的互斥性。

// 使用商品ID作為key
key = itemID
// 使用客戶端唯一標識作為value
val = clientUniqueID
//申請分布式鎖,Timeout是超時時間
lock =acquireLock(key, val, Timeout)
//當拿到鎖后,才能進行庫存查驗和扣減
if(lock == True) {
   //庫存查驗和扣減
   availStock = DECR(key, k)
   //庫存已經扣減完了,釋放鎖,返回秒殺失敗
   if (availStock < 0) {
      releaseLock(key, val)
      return error
   }
   //庫存扣減成功,釋放鎖
   else{
     releaseLock(key, val)
     //訂單處理
   }
}
//沒有拿到鎖,直接返回
else
   return

使用分布式鎖時,客戶端要先向Redis請求鎖,只有請求到鎖,才能進行庫存查驗等操作,這樣客戶端在爭搶分布式鎖時,大部分秒殺請求本身就會因為搶不到鎖而被攔截。

推薦使用切片集群中的不同實例來分別保存分布式鎖和商品庫存信息。秒殺請求會先訪問保存分布式鎖的實例。若客戶端沒拿到鎖,這些客戶端就不會查詢商品庫存,減輕保存庫存信息的實例的壓力。

6 總結

秒殺系統是個系統性工程,Redis實現對庫存查驗、扣減環節的支撐。

此外,還有環節需要處理:

前端靜態頁面的設計

秒殺頁面上能靜態化處理的頁面元素,要盡量靜態化,充分利用CDN或瀏覽器緩存服務秒殺開始前的請求

請求攔截和流控

在秒殺系統的接入層,對惡意請求進行攔截,避免對系統的惡意攻擊,例如使用黑名單禁止惡意IP進行訪問。如果Redis實例的訪問壓力過大,為了避免實例崩潰,我們也需要在接入層進行限流,控制進入秒殺系統的請求數量。

庫存信息過期時間處理

Redis中保存的庫存信息其實是數據庫的緩存,為了避免緩存擊穿問題,不要給庫存信息設置過期時間。
數據庫訂單異常處理。如果數據庫沒能成功處理訂單,可以增加訂單重試功能,保證訂單最終能被成功處理。

資源隔離

秒殺活動帶來的請求流量巨大,我們需要把秒殺商品的庫存信息用單獨的實例保存,而不要和日常業務系統的數據保存在同一個實例上,這樣可以避免干擾業務系統的正常運行。

原文鏈接:https://blog.csdn.net/qq_33589510/article/details/121127075

欄目分類
最近更新