skuid是什么意思?商品id和skuid的區別解析

電商庫存系統的防超賣和高并發扣減方案

摘要:如果你要開發一個電商庫存系統,最擔心的是什么?閉上眼睛想下,當然是高并發和防超賣了!本文給出一個統籌考慮如何高并發和防超賣數據準確性的方案。讀者可以直接借鑒本設計,或在此基礎上做出更切合使用場景的設計。

下面用電商庫存為示例,來說明如何高并發扣減庫存,原理同樣適用于其他需要并發寫和數據一致性的場景。

庫存數量模型示例

為了描述方便,我們使用簡化的庫存數量模型,真實場景中庫存數據項會比我的示例多很多,但已經夠說明原理。如下表,庫存數量表(stockNum)包含商品標識和庫存數量兩個字段,庫存數量代表有多少貨可以賣。

字段名

英文名

字段類型

商品標識

skuId

長整型

庫存數量

num

整數

傳統通過數據庫保證不超賣

庫存管理的傳統方案為了保證不超賣,都是使用數據庫的事務來保證的:通過Sql判斷剩余的庫存數夠用,多個并發執行update語句只有一個能執行成功;為了保證扣減不重復,會配合一個防重表來防止重復的提交,做到冪等性,防重表示例(antiRe)設計如下:

字段名

英文名

字段類型

標識

id

長整型

防重碼

code

字符串(唯一索引)

比如一個下單過程的扣減過程示例如下:

事務開始
Insert into antiRe(code) value (‘訂單號+Sku’)
Update stockNum set num=num-下單數量 where skuId=商品ID and num-下單數量>0
事務結束
復制代碼

面臨系統流量越來越大,數據庫的性能瓶頸就會暴露出來:就算分庫分表也是沒用的,促銷的時候高并發都是針對少量商品的,最終并發流量會打向少數表,只能去提升單分片的抗量能力。我們接下來設計一種使用Redis緩存做庫存扣減的方案。

綜合使用數據庫和Redis滿足高并發扣減的原理

扣減庫存其實包含兩個過程:第一步是超賣校驗,第二步是扣減數據的持久化;在傳統數據庫扣減中,兩步是一起完成的。抗寫的實現原理其實是巧妙的利用了分離的思想,分離開防超賣和數據持久化;首先防超賣是由Redis來完成的;通過Redis防超賣后,只要落庫就可以;落庫通過任務引擎,業務數據庫使用商品分庫分表,任務引擎任務通過單據號分庫分表,熱點商品的落庫會被狀態機分散開,消除熱點。

整體架構如下:

skuid是什么意思?商品id和skuid的區別解析

第一關解決超賣檢驗:我們可以把數據放入Redis中,每次扣減庫存,都對Redis中的數據進行incryby 扣減,如果返回的數量大于0,說明庫存夠,因為Redis是單線程,可以信任返回結果。第一關是Redis,可以抗高并發,性能Ok。超賣校驗通過后,進入第二關。

第二關解決庫存扣減:經過第一關后,第二關不需要再判斷數量是否足夠,只需要傻瓜扣減庫存就行,對數據庫執行如下語句,當然還是需要處理防重冪等的,不需要判斷數量是否大于0了,扣減SQL只要如下寫就可以。

事務開始
Insert into antiRe(code) value (‘訂單號+Sku’)
Update stockNum set num=num-下單數量 where skuId=商品ID
事務結束
復制代碼

要點:最終還是要使用數據庫,熱點怎么解決的呢?任務庫使用訂單號進行分庫分表,這樣針對同一個商品的不同訂單會散列在任務庫的不同庫存,雖然還是數據庫抗量,但已經消除了數據庫熱點。

整體交互序列圖如下:

skuid是什么意思?商品id和skuid的區別解析

熱點防刷

但Redis也是有瓶頸的,如果出現過熱SKU就會打向Redis單片,會造成單片性能抖動。庫存防刷有個前提是不能卡單的。可以定制設計JVM內毫秒級時間窗的限流,限流的目的是保護Redis,盡可能的不限流。限流的極端情況就是商品本來應該在一秒內賣完,但實際花了兩秒,正常并不會發生延遲銷售,之所以選擇JVM是因為如果采用遠端集中緩存限流,還未來得及收集數據就已經把Redis打死。

實現方案可以通過guava之類的框架,每10ms一個時間窗,每個時間窗進行計數,單臺服務器超過計數進行限流。比如10ms超過2個就限流,那么一秒一臺服務器就是200個,50臺服務器一秒就可以賣出1萬個貨,自己根據實際情況調整閾值就可以。

skuid是什么意思?商品id和skuid的區別解析

Redis扣減原理

Redis的incrby 命令可以用做庫存扣減,扣減項可能多個,我們使用Hash結構的hincrby命令,先用Reids原生命令模擬整個過程,為了簡化模型我們演示一個數據項的操作,多個數據項原理完全等同。

127.0.0.1:6379> hset iphone inStock 1 #設置蘋果手機有一個可售庫存
(integer) 1
127.0.0.1:6379> hget iphone inStock   #查看蘋果手機可售庫存為1
"1"
127.0.0.1:6379> hincrby iphone inStock -1 #賣出扣減一個,返回剩余0,下單成功
(integer) 0
127.0.0.1:6379> hget iphone inStock #驗證剩余0
"0"
127.0.0.1:6379> hincrby iphone inStock -1 #應用并發超賣但Redis單線程返回剩余-1,下單失敗
(integer) -1
127.0.0.1:6379> hincrby iphone inStock 1 #識別-1,回滾庫存加一,剩余0
(integer) 0
127.0.0.1:6379> hget iphone inStock #庫存恢復正常
"0"
復制代碼

扣減的冪等性保證

如果應用調用Redis扣減后,不知道是否成功,可以針對批量扣減命令增加一個防重碼,對防重碼執行setnx命令,當發生異常的時候,可以根據防重碼是否存在來決定是否扣減成功,針對批量命名可以使用pipeline提高成功率。

// 初始化庫存
127.0.0.1:6379> hset iphone inStock 1 #設置蘋果手機有一個可售庫存
(integer) 1
127.0.0.1:6379> hget iphone inStock   #查看蘋果手機可售庫存為1
"1"

// 應用線程一扣減庫存,訂單號a100,jedis開啟pipeline
127.0.0.1:6379> set a100_iphone "1" NX EX 10 #通過訂單號和商品防重碼
OK
127.0.0.1:6379> hincrby iphone inStock -1 #賣出扣減一個,返回剩余0,下單成功
(integer) 0
//結束pipeline,執行結果OK和0會一起返回
復制代碼

防止并發扣減后校驗:為了防止并發扣減,需要對Redis的hincrby命令返回值是否為負數,來判斷是否發生高并發超賣,如果扣減后的結果為負數,需要反向執行hincrby,把數據進行加回。

如果調用中發生網絡抖動,調用Redis超時,應用不知道操作結果,可以通過get命令來查看防重碼是否存在來判斷是否扣減成功。

127.0.0.1:6379> get a100_iphone   #扣減成功
"1"
127.0.0.1:6379> get a100_iphone   #扣減失敗
(nil)
復制代碼

單向保證

在很多場景中,因為沒有使用事務,你很那做到不超賣,并且不少賣,所以在極端情況下,我的抉擇是選擇不超賣,但有可能少賣。當然還是應該盡量保證數據準確,不超賣,也不少賣;不能完全保證的前提下,選擇不超賣單向保證,也要通過手段來盡可能減少少賣的概率。

比如如果扣減Redis過程中,命令編排是先設置防重碼,再執行扣減命令失敗;如果執行過程網絡抖動可能放重碼成功,而扣減失敗,重試的時候就會認為已經成功,造成超賣,所以上面的命令順序是錯誤的,正確寫法應該是:

如果是扣減庫存,順序為:1.扣減庫存 2.寫入放重碼。

如果是回滾庫存,順序為 1.寫入放重碼 2.扣減庫存。

為什么使用PiPeline

在上面命令中,我們使用了Redis的Pipeline,來看下Pipeline的原理。

非pipeline模式 request–>執行 –>response request–>執行 –>response pipeline模式 request–>執行 server將響應結果隊列化 request–>執行 server將響應結果隊列化 –>response –>response

使用Pipeline,能盡量保證多條命令返回結果的完整性,讀者可以考慮使用Redis事務來代替Pipeline,實際項目中,個人有過Pipeline的成功抗量經驗,并沒有使用Redis事務,正常情況下事務比pipeline慢一些,所以沒有采用。

Redis事務 1)mutil:開啟事務,此后的所有操作將被添加到當前鏈接事務的“操作隊列”中 2)exec:提交事務 3)discard:取消隊列執行 4)watch:如果watch的key被修改,觸發dicard。

通過任務引擎實現數據庫的最終一致性

前面通過任務引擎來保證數據一定持久化數據庫,「任務引擎」的設計如下,我們把任務調度抽象為業務無關的框架。「任務引擎」可以支持簡單的流程編排,并保證至少成功一次。「任務引擎」也可以作為狀態機的引擎出現,支持狀態機的調度,所以「任務引擎」也可以稱為「狀態機引擎」,在此文是同一個概念。

**任務引擎設計核心原理:**先把任務落庫,通過數據庫事務保證子任務拆分和父任務完成的事務一致性。

**任務庫分庫分表:**任務庫使用分庫分表,可以支撐水平擴展,通過設計分庫字段和業務庫字段不同,無數據熱點。

任務引擎的核心處理流程:

skuid是什么意思?商品id和skuid的區別解析

**第一步:**同步調用提交任務,先把任務持久化到數據庫,狀態為「鎖定處理」,保證這件事一定得到處理。

注:原來的最初版本,任務落庫是待處理,然后由掃描Worker進行掃描,為了防止并發重復處理,掃描后進行單個任務鎖定,鎖定成功再進行處理。后來優化為落庫任務直接標識狀態為「鎖定處理」,是為了性能考慮,省去重新掃描再搶占任務,在進程內直接通過線程異步處理。

鎖定Sql參考:

UPDATE 任務表_分表號 SET 狀態 = 100,modifyTime = now() WHERE id = #{id} AND 狀態 = 0
復制代碼

**第二步:**異步線程調用外部處理過程,調用外部處理完成后,接收返回子任務列表。通過數據庫事務把父任務狀態設置為已經完成,子任務落庫。并把子任務加入線程池。

要點:保證子任務生成和父任務完成的事務性

**第三步:**子任務調度執行,并重新把新子任務落庫,如果沒有子任務返回,則整個流程結束。

異常處理Worker

異常解鎖Worker來把長時間未處理完成的任務解鎖,防止因為服務器重啟,或線程池滿造成的任務一直鎖定無服務器執行。

補漏Worker防止服務器重啟造成的線程池任務未執行完成,補漏程序重新鎖定,觸發執行。

任務狀態轉換過程

skuid是什么意思?商品id和skuid的區別解析

任務引擎數據庫設計

任務表數據庫結構設計示例(僅做示例使用,真實使用需要完善)

字段

類型

說明

任務ID標識

Long

主鍵

狀態

Int

0待處理,100鎖定處理,1完成

數據

String

Json格式的業務數據

執行時間

Date

執行時間

任務引擎數據庫容災:

任務庫使用分庫分表,當一個庫宕機,可以把路由到宕機庫的流量重新散列到其他存活庫中,可以手工配置,或通過系統監控來自動化容災。如下圖,當任務庫2宕機后,可以通過修改配置,把任務庫2流量路由到任務庫1和3。補漏引擎繼續掃描任務庫2是因為當任務庫2通過主從容災恢復后,任務庫2宕機時未來的及處理的任務可以得到補充處理。

任務引擎調度舉例

比如用戶購買了兩個手機和一個電腦,手機和電腦分散在兩個數據庫,通過任務引擎先持久化任務,然后驅動拆分為兩個子任務,并最終保證兩個子任務一定成功,實現數據的最終一致性。整個執行過程的任務編排如下:

skuid是什么意思?商品id和skuid的區別解析

任務引擎交互流程:

skuid是什么意思?商品id和skuid的區別解析

差異對比-異構數據的終極解決方案

只要有異構,一定會有差異的,為了保證差異的影響可控,終極方案還是要靠差異對比來解決。本文篇幅所限,不再展開,后續再單獨成文。DB和Redis差異對比的大概過程為:接收庫存變化消息,不斷跟進對比Redis和DB的數據是否一致,如果連續穩定不一致,則進行數據修復,用DB數據來修改Redis的數據。

聲明:本文由網站用戶香香發表,超夢電商平臺僅提供信息存儲服務,版權歸原作者所有。若發現本站文章存在版權問題,如發現文章、圖片等侵權行為,請聯系我們刪除。

(1)
上一篇 2023年2月7日 14:04:49
下一篇 2023年2月7日 14:14:53

相關推薦

發表回復

您的電子郵箱地址不會被公開。 必填項已用*標注

主站蜘蛛池模板: 欧美一区二区三区综合色视频 | 娇喘午夜啪啪五分钟娇喘| 好吊色欧美一区二区三区视频| 久久久免费精品| 暖暖直播在线观看| 亚洲国产精品白丝在线观看| 漂亮人妻被黑人久久精品| 免费看片A级毛片免费看| 老司机亚洲精品影视www| 国产人妖XXXX做受视频| 日本最大色倩网站www| 国产精品成人免费视频网站| 99re6精品| 坐公交车弄了2个小时小视频| 一本一道波多野结衣大战黑人| 成熟女人牲交片免费观看视频| 久久久久亚洲av无码尤物| 日韩在线观看网址| 九色综合狠狠综合久久| 欧美A级毛欧美1级a大片免费播放 欧美BBBWBBWBBWBBW | 免费观看性行为视频的网站| 中文字幕在线免费看| 日韩成人免费aa在线看| 亚洲av无码一区二区三区在线播放| 欧美成人在线免费观看| 亚洲欧洲自拍拍偷午夜色| 波多野结衣33分钟办公室jian情| 人妻少妇偷人精品视频| 男女一边摸一边做爽爽| 免费精品国产自产拍观看| 精品国产一区二区麻豆| 北美伦理电线在2019| 精品熟女碰碰人人a久久| 另类人妖与另类欧美| 国产激爽大片高清在线观看| 大陆一级毛片免费视频观看i| www.尤物视频| 天天综合色天天桴色| gⅴh372hd禁断介护老人| 天天操夜夜操天天操| a级特黄毛片免费观看|