系統(tǒng)實(shí)踐:設(shè)計(jì)滑動(dòng)庫存編號(hào)分布式鎖,處理活動(dòng)秒殺場景)
文檔教程后端【免費(fèi)下載鏈接】CodeGuide:books: 本代碼庫是作者小傅哥多年從事一線互聯(lián)網(wǎng) Java 開發(fā)的學(xué)習(xí)歷程技術(shù)匯總旨在為大家提供一個(gè)清晰詳細(xì)的學(xué)習(xí)教程側(cè)重點(diǎn)更傾向編寫Java核心內(nèi)容。如果本倉庫能為您提供幫助請(qǐng)給予支持(關(guān)注、點(diǎn)贊、分享)項(xiàng)目地址https://gitcode.com/gh_mirrors/code/CodeGuide點(diǎn)擊查看免費(fèi)下載導(dǎo)讀當(dāng)大量用戶同時(shí)參與抽獎(jiǎng)活動(dòng)時(shí)活動(dòng)本身就演變成了秒殺場景——集中化的庫存扣減如果直接落在數(shù)據(jù)庫上TPS 一旦達(dá)到 1k2k 就會(huì)把數(shù)據(jù)庫拖垮。本文以 Lottery 分布式抽獎(jiǎng)系統(tǒng)第 19 節(jié)的實(shí)現(xiàn)為主線講解如何引入 Redis 替換原有的數(shù)據(jù)庫行級(jí)鎖方案并通過“滑動(dòng)庫存編號(hào)”將鎖的顆粒度從活動(dòng) ID 細(xì)化到庫存編號(hào)維度最后結(jié)合 MQ 異步消息完成緩存與數(shù)據(jù)庫庫存的最終一致性處理。讀完本文你將掌握秒殺場景下從“獨(dú)占鎖”到“滑塊鎖”的演進(jìn)思路、庫存編號(hào)加鎖的設(shè)計(jì)方法以及緩存扣減 MQ 落庫的一致性落地套路。一、背景為什么活動(dòng)參與需要分布式鎖抽獎(jiǎng)系統(tǒng)的核心鏈路是“領(lǐng)取活動(dòng) → 執(zhí)行抽獎(jiǎng) → 落庫結(jié)果”其中領(lǐng)取活動(dòng)環(huán)節(jié)涉及活動(dòng)庫存的扣減。在第 19 節(jié)之前這部分庫存扣減依賴的是數(shù)據(jù)庫行級(jí)鎖例如通過UPDATE activity SET surplus surplus - 1 WHERE activity_id ? AND surplus 0這類語句依賴數(shù)據(jù)庫行鎖保證不超賣。但行級(jí)鎖的問題在于數(shù)據(jù)庫行鎖在集中熱點(diǎn)行上會(huì)形成嚴(yán)重競爭TPS 到 1k2k 時(shí)數(shù)據(jù)庫連接與鎖等待就會(huì)把系統(tǒng)拖垮秒殺場景并發(fā)峰值集中、持續(xù)時(shí)間短抽獎(jiǎng)活動(dòng)通常 push 發(fā)出后 13 分鐘結(jié)束數(shù)據(jù)庫根本不是為這種瞬時(shí)集中寫設(shè)計(jì)的。因此第 19 節(jié)的核心改造是把庫存扣減的動(dòng)作從數(shù)據(jù)庫前移到 Redis用 Redis 分布式鎖 緩存扣減處理集中化庫存競爭。相關(guān)上下文可參閱 第11節(jié)聲明事務(wù)領(lǐng)取活動(dòng)領(lǐng)域開發(fā)原有領(lǐng)取活動(dòng)領(lǐng)域的事務(wù)與庫存校驗(yàn)邏輯以及 第12節(jié)在應(yīng)用層編排抽獎(jiǎng)過程領(lǐng)取活動(dòng)在整體流程編排中的位置。二、環(huán)境準(zhǔn)備引入 Redis 服務(wù)由于本章需要用到 Redis需要在云服務(wù)器或本地搭建 Redis 服務(wù)。如果暫時(shí)沒有云服務(wù)器在本地搭建 Redis 也可以只是會(huì)少一些云環(huán)境的配置練習(xí)。Redis 環(huán)境就緒后在抽獎(jiǎng)系統(tǒng)中引入 Redis 模塊用于支撐用戶參與抽獎(jiǎng)活動(dòng)的庫存扣減優(yōu)化。三、鎖的顆粒度設(shè)計(jì)從獨(dú)占鎖到滑動(dòng)庫存編號(hào)3.1 為什么不能直接鎖活動(dòng)編號(hào)一個(gè)最直覺的方案是對(duì)活動(dòng)編號(hào)如100001加鎖這就是獨(dú)占鎖獨(dú)占鎖針對(duì)于活動(dòng) ID 加鎖。但第 19 節(jié)明確指出了它的缺陷“不要把鎖直接放到活動(dòng)編號(hào)上這樣在極端臨界情況下會(huì)出現(xiàn)秒殺解鎖失敗導(dǎo)致庫存有剩余但不能下單的情況?!豹?dú)占鎖的粒度是整個(gè)活動(dòng)所有參與該活動(dòng)的用戶串行排隊(duì)競爭同一把鎖一方面吞吐極低另一方面在極端臨界情況下鎖釋放與重新獲取的時(shí)序問題可能造成“庫存有剩余但用戶無法下單”的事故。在 notes.md 的面試問答“秒殺的滑塊鎖講解”中對(duì)兩類鎖的定位講得很清楚獨(dú)占鎖是加給個(gè)人流程的——無資源競爭如貸款單受理分段/滑塊/無鎖化是加給庫存的——有資源競爭如秒殺、商品發(fā)貨等集中資源類場景。3.2 滑動(dòng)庫存編號(hào)加鎖的設(shè)計(jì)第 19 節(jié)的優(yōu)化方向是增加鎖的顆粒度以滑動(dòng)庫存剩余編號(hào)的方式進(jìn)行加鎖。例如活動(dòng)編號(hào)為100001庫存編號(hào)則形如100001_1 100001_2 100001_3 ...每個(gè)庫存編號(hào)對(duì)應(yīng)一把獨(dú)立的鎖用戶參與活動(dòng)時(shí)先通過滑動(dòng)窗口獲取一個(gè)可用的庫存編號(hào)再針對(duì)該編號(hào)加鎖并執(zhí)行扣減。這樣多個(gè)用戶可以并行搶不同編號(hào)的鎖避免了獨(dú)占鎖在活動(dòng)編號(hào)上的串行競爭鎖的競爭面從“1 個(gè)活動(dòng)”攤薄到“N 個(gè)庫存編號(hào)”并發(fā)能力隨庫存量線性擴(kuò)展避免了極端臨界情況下獨(dú)占鎖釋放異常導(dǎo)致整個(gè)活動(dòng)無法下單的問題?;瑝K鎖的核心目標(biāo)是去競態(tài)避免獨(dú)占鎖影響系統(tǒng)的整體響應(yīng)性能。這也是面試高頻考點(diǎn)“Redis 滑動(dòng)庫存分布式鎖是如何實(shí)現(xiàn)的”的標(biāo)準(zhǔn)答案素材。3.3 為什么加了鎖還要用 incr 扣減一個(gè)常見疑問是滑動(dòng)庫存編號(hào)都有了直接用incr扣減不就行了為什么還要加鎖答案是加鎖是兜底。你不知道什么時(shí)候會(huì)出現(xiàn) incr 結(jié)果不對(duì)的情況例如Redis 集群配置問題特例出現(xiàn) Redis 問題后需要恢復(fù)庫存沒有鎖保護(hù)時(shí)并發(fā)下可能出現(xiàn)超賣。所以滑動(dòng)庫存編號(hào)鎖的意義在于即使 incr 本身很快就像公共衛(wèi)生間的一個(gè)坑一個(gè)門誰進(jìn)去誰鎖上沒有就跑到下一個(gè)門也需要“鎖門”來保證并發(fā)語義的正確性。四、扣減流程緩存扣減 MQ 異步落庫4.1 兩級(jí)庫存設(shè)計(jì)第 19 節(jié)把庫存扣減拆成了兩個(gè)階段緩存扣減Redis用戶領(lǐng)取活動(dòng)時(shí)直接在 Redis 中對(duì)滑動(dòng)庫存編號(hào)執(zhí)行扣減。這一步扛住秒殺峰值流量。數(shù)據(jù)庫扣減異步緩存扣減完成后數(shù)據(jù)庫中的庫存其實(shí)并沒有扣減需要發(fā)送一條 MQ 消息來異步更新數(shù)據(jù)庫中的活動(dòng)庫存。這一設(shè)計(jì)的核心動(dòng)機(jī)MQ 天然具備消峰能力。降低 MQ 分片的情況下消費(fèi)效率有所下降但不會(huì)對(duì)數(shù)據(jù)庫造成壓力通過異步落庫保證最終數(shù)據(jù)一致性即可如果并發(fā)體量更大MQ 的消費(fèi)端還可以不直接更新數(shù)據(jù)庫而是先更新到緩存再由定時(shí)任務(wù)在最終階段同步落庫進(jìn)一步減少對(duì)數(shù)據(jù)庫表的操作。4.2 與原有流程的對(duì)比改造前的領(lǐng)取活動(dòng)流程依賴數(shù)據(jù)庫行級(jí)鎖完成庫存扣減參見 第11節(jié)聲明事務(wù)領(lǐng)取活動(dòng)領(lǐng)域開發(fā)存在并發(fā)瓶頸改造后用戶領(lǐng)取活動(dòng) → Redis 滑動(dòng)庫存編號(hào)加鎖去競態(tài) → 緩存庫存扣減承載秒殺流量 → 發(fā)送 MQ 消息 → MQ 異步消費(fèi)更新數(shù)據(jù)庫活動(dòng)庫存最終一致性其中 MQ 解耦的整體思路延續(xù)自 第16節(jié)使用MQ解耦抽獎(jiǎng)發(fā)貨流程——抽獎(jiǎng)系統(tǒng)把“抽獎(jiǎng)”和“發(fā)獎(jiǎng)”用 MQ 消息串聯(lián)避免單個(gè)流程過長導(dǎo)致用戶一直等待本節(jié)則把同一思想應(yīng)用到“緩存扣減”與“數(shù)據(jù)庫扣減”之間。補(bǔ)充與異常兜底銜接的是 第18節(jié)掃描庫表補(bǔ)償發(fā)貨單MQ消息 中基于 xxl-job 的定時(shí)掃描補(bǔ)償思路——凡是依賴 MQ 異步落庫的狀態(tài)都需要有任務(wù)掃描補(bǔ)償機(jī)制兜底保證全流程可靠性。如果 MQ 消費(fèi)失敗導(dǎo)致數(shù)據(jù)庫庫存未扣減同樣可以借助定時(shí)任務(wù)對(duì)賬補(bǔ)償。五、設(shè)計(jì)原則與工程取舍結(jié)合 notes.md 中的總結(jié)本節(jié)落地的核心取舍如下維度獨(dú)占鎖不推薦滑動(dòng)庫存編號(hào)鎖本章方案鎖粒度整個(gè)活動(dòng) ID單個(gè)庫存編號(hào)如 100001_1競爭面全活動(dòng)串行競爭按庫存編號(hào)并行競爭吞吐低隨并發(fā)下降高隨庫存量擴(kuò)展風(fēng)險(xiǎn)極端臨界解鎖失敗庫存剩余但無法下單并行搶不同編號(hào)鎖去競態(tài)適用個(gè)人流程類無資源競爭集中資源類秒殺、庫存工程層面的取舍還要注意響應(yīng)優(yōu)先對(duì)于非交易的活動(dòng)類場景要的就是一個(gè)“快”——快速響應(yīng)、快速釋放可接受容錯(cuò)失敗概率但不能影響主核心交易鏈路。營銷秒殺場景的根本訴求是保證不超賣。鎖不是萬能incr 滑動(dòng)編號(hào)鎖解決的是并發(fā)扣減的正確性最終一致性靠 MQ 異步落庫 定時(shí)任務(wù)兜底而不是靠強(qiáng)一致事務(wù)。鎖的恢復(fù)場景當(dāng) Redis 出現(xiàn)異常需要恢復(fù)庫存時(shí)分布式鎖是安全兜底防止恢復(fù)過程中超賣。六、總結(jié)第 19 節(jié)完成了一次典型的秒殺場景優(yōu)化改造引入 Redis把集中化庫存競爭從數(shù)據(jù)庫前移到緩存層從活動(dòng) ID 獨(dú)占鎖優(yōu)化為滑動(dòng)庫存編號(hào)分布式鎖100001_1、100001_2、100001_3…把鎖顆粒度細(xì)化到庫存編號(hào)去競態(tài)、提吞吐、避免極端臨界事故緩存扣減 MQ 異步更新數(shù)據(jù)庫利用 MQ 消峰特性降低對(duì)數(shù)據(jù)庫的壓力以最終一致性取代強(qiáng)一致。這套“滑塊鎖 緩存扣減 MQ 落庫”的組合是營銷秒殺場景的經(jīng)典解法。面試中常見的“Redis 滑動(dòng)庫存分布式鎖如何實(shí)現(xiàn)”“為什么不用獨(dú)占鎖”“incr 就能扣庫存為什么還要加鎖”“緩存扣了但 MQ 沒發(fā)出去怎么辦”等追問都可以在 notes.md 的面試問答部分找到對(duì)應(yīng)的話術(shù)與思考方向可作為復(fù)習(xí)素材結(jié)合本倉庫 Lottery 抽獎(jiǎng)系統(tǒng)介紹 繼續(xù)深入。贊分享文檔教程后端【免費(fèi)下載鏈接】CodeGuide:books: 本代碼庫是作者小傅哥多年從事一線互聯(lián)網(wǎng) Java 開發(fā)的學(xué)習(xí)歷程技術(shù)匯總旨在為大家提供一個(gè)清晰詳細(xì)的學(xué)習(xí)教程側(cè)重點(diǎn)更傾向編寫Java核心內(nèi)容。如果本倉庫能為您提供幫助請(qǐng)給予支持(關(guān)注、點(diǎn)贊、分享)項(xiàng)目地址https://gitcode.com/gh_mirrors/code/CodeGuide點(diǎn)擊查看免費(fèi)下載相關(guān)推薦在 Lottery 抽獎(jiǎng)系統(tǒng)中引入 XXL-JOB分布式任務(wù)調(diào)度處理活動(dòng)狀態(tài)掃描在 Lottery 抽獎(jiǎng)系統(tǒng)中引入 XXL JOB分布式任務(wù)調(diào)度處理活動(dòng)狀態(tài)掃描 本文以 Lottery 分布式抽獎(jiǎng)系統(tǒng)DDD 四層架構(gòu)為背景講解如何在文檔教程后端Log-Lottery3D球體互動(dòng)抽獎(jiǎng)系統(tǒng)的技術(shù)解析與場景實(shí)踐Log Lottery3D球體互動(dòng)抽獎(jiǎng)系統(tǒng)的技術(shù)解析與場景實(shí)踐 在2023年某互聯(lián)網(wǎng)公司年會(huì)上傳統(tǒng)的抽獎(jiǎng)箱抽獎(jiǎng)方式讓現(xiàn)場氛圍一度陷入沉悶——當(dāng)主持人從紙箱中前端桌面應(yīng)用抽獎(jiǎng)系統(tǒng)數(shù)據(jù)庫設(shè)計(jì)實(shí)戰(zhàn)從活動(dòng)、策略到分庫分表的 7 組表設(shè)計(jì)Lottery 項(xiàng)目抽獎(jiǎng)系統(tǒng)數(shù)據(jù)庫設(shè)計(jì)實(shí)戰(zhàn)從活動(dòng)、策略到分庫分表的 7 組表設(shè)計(jì)Lottery 項(xiàng)目 在基于 DDD 四層架構(gòu)的 Lottery 抽獎(jiǎng)系統(tǒng)中庫表設(shè)計(jì)是整個(gè)項(xiàng)文檔教程后端創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考