秒殺場景實踐之搶紅包(一) —— 常用解決方案
- 2019 年 12 月 20 日
- 筆記
.example_responsive_1 { width: 200px; height: 50px; } @media(min-width: 290px) { .example_responsive_1 { width: 270px; height: 50px; } } @media(min-width: 370px) { .example_responsive_1 { width: 339px; height: 50px; } } @media(min-width: 500px) { .example_responsive_1 { width: 468px; height: 50px; } } @media(min-width: 720px) { .example_responsive_1 { width: 655px; height: 50px; } } @media(min-width: 800px) { .example_responsive_1 { width: 728px; height: 50px; } } (adsbygoogle = window.adsbygoogle || []).push({});
秒殺場景實踐之搶紅包(一) —— 常用解決方案
目錄
- 秒殺場景實踐之搶紅包(一) —— 常用解決方案
- 前言
- 分析
- 場景
- 業務
- 技術
- 方案一 —— 預分配
- 適用場景
- 簡要描述
- 實現細節
- 流程
- 備註
- 方案二 —— 實時分配
- 適用場景
- 實現細節
- 流程
- 備註
- 細節及優化
- 結語
秒殺場景實踐之搶紅包常用解決方案 文章地址: https://blog.piaoruiqing.com/2019/09/01/high-concurrent-red-envelope/
前言
秒殺場景在生活中幾乎隨處可見, 不論是商品搶購、春運搶票還是一個隨處可見的紅包, 都會涉及到秒殺的場景. 在面試中, 秒殺業務的設計也成為熱門題目為面試官和應聘者津津樂道.
接下來, 本文將針對秒殺場景中的搶紅包實現方案進行分享, 包括紅包業務常見的實現方案, 瓶頸及優化.
分析
場景
紅包的應用場景有很多, 如隨機紅包、定額紅包等, 甚至還有結合其他促銷業務的紅包變種如搶購物津貼等. 但從技術的角度來看, 不論玩法有多少變化, 其核心都是相似的:
- 穩定: 扛得住突發的大流量, 確保紅包都能成功派發.
- 準確: 數據一定要正確, 不能出現超額派發的情況.
業務
搶紅包可能會由於業務需求不同而產生很多變種, 設計上要足夠抽象, 不能為了搶現金紅包和搶購物津貼紅包寫多份相似的程式碼. 搶到紅包的後置操作可以作為消息, 由不同的業務模組自行處理.
技術
搶紅包核心業務不複雜, 其關鍵點在於應對高並發、資源爭用等.
- 高並發: 非同步、橫向擴展負載均衡、限流等.
- 讀多寫少: 快取.
- 資源爭用: 原子操作, 快取或資料庫等層面可進行控制. 如使用Lua腳本進行減庫存操作.
方案一 —— 預分配
適用場景
紅包數量相對合理, 很少產生庫存剩餘的情況、用戶量級不大的情況.
- 優勢: 實現簡單、配合快取很容易應對高並發
- 不足: 頻繁發放較多數量大的紅包會導致一次性寫入大量分配記錄, 如果領取的人不多, 會產生很多無效數據.
簡要描述
預分配是在發放紅包時, 根據紅包總額和數量、按照既定演算法進行分配, 提前創建好全部的紅包分配記錄. 領取時只是將紅包分配記錄進行更新.
比較適合系統發放的紅包(面向某一標籤的全部用戶群體, 發出的紅包基本會被領取完), 不適合用戶群組紅包(無法控制領取紅包人數, 當紅包個數遠大於群組人數的情況下, 無效數據比較多, 比如在一個10人群組發放一個數量為1000的紅包).
實現細節
graph LR A(創建紅包, 個數為N) --> B(預分配N條紅包領取記錄) B --> C(開始搶紅包) C --> D(先到先得)
流程
- 在紅包開搶前, 預先分配好紅包領取記錄, 領取記錄的用戶ID為負值.
- 開搶後, 開放唯一領取紅包的入口
- 領取操作核心就是更新紅包分配記錄: — 此處劃重點 ( ̄▽ ̄)" UPDATE IGNORE record SET user_id = {userId}, gmt_receive = UNIX_TIMESTAMP() WHERE red_envelop_id = 1 AND user_id < 0 LIMIT 1; — red_envelop_id + user_id 有唯一約束
紅包發放記錄
ID |
總金額 |
數量 |
… |
---|---|---|---|
1 |
100 |
3 |
… |
紅包分配記錄
unique:
紅包ID
+領取用戶ID
ID |
紅包ID |
金額 |
領取用戶ID |
領取時間 |
… |
---|---|---|---|---|---|
1 |
1 |
10 |
-1 |
0 |
… |
2 |
1 |
60 |
-2 |
0 |
… |
3 |
1 |
30 |
-3 |
0 |
… |
備註
UPDATE IGNORE ... LIMIT 1
: 解決了資源爭用問題, 確保並發請求下紅包的領取的數據正確性.red_envelop_id
+user_id
: 創建索引並唯一約束, 確保對於同一個紅包同一用戶只能領取一次.- 預分配的
user_id
為負值: 因為red_envelop_id
+user_id
有唯一約束. - 對於一般流量的小型活動, 這種方式實現簡單、成本低, 不引入快取的情況下只用一個MySQL基本也能扛得住.
[版權聲明] 本文發佈於朴瑞卿的部落格, 允許非商業用途轉載, 但轉載必須保留原作者朴瑞卿 及鏈接:https://blog.piaoruiqing.com. 如有授權方面的協商或合作, 請聯繫郵箱: [email protected].
方案二 —— 實時分配
適用場景
領取人數無法估計、頻發退款, 如群組紅包(經常發生剩餘退款)
實現細節
graph LR A(創建紅包, 個數為N) --> B(首次寫入快取) B --> C(搶紅包) C --> D(讀取或載入快取) D --> E(分配紅包金額) E --> F(寫入快取--原子操作) F --> G(落庫)
流程
- 開搶前將紅包資訊載入到快取, 首次載入時間可長一些
- 搶紅包: 從快取讀取(沒有則載入), 分配紅包後原子更新快取(若已發放完畢則直接返回失敗)
- 快取更新後寫入資料庫(校驗數據正確性)
紅包發放記錄
ID |
總金額 |
數量 |
剩餘金額 |
剩餘數量 |
… |
---|---|---|---|---|---|
1 |
100 |
3 |
100 |
3 |
… |
紅包分配記錄
unique:
紅包ID
+領取用戶ID
ID |
紅包ID |
金額 |
領取用戶ID |
領取時間 |
… |
---|---|---|---|---|---|
|
… |
… |
… |
… |
… |
備註
- 首次快取載入時間要稍長一點: 紅包剛開始發放時可能會有較大的突發流量, 此時去DB載入快取不合適.
- 快取可以不用和資料庫保證強一致: 數據的正確性由資料庫進行維護, 如: 快取扣除了紅包額度, 但更新資料庫時發生了異常, 此時快取不需要回滾, 待快取失效後重新載入即可.(所以快取時間可以是幾秒鐘, 不用太長)
- 更新快取可以考慮使用Lua腳本以保證原子性.
- 實時分配紅包不會產生無效的記錄, 適合大多數場景, 但實現比預分配複雜的多.
細節及優化
- 客戶端點擊頻率控制能在一定程度上減少流量.
- 紅包領光後在快取一層攔截掉全部請求, 直接返回失敗.
- 網關層進行限流.
結語
秒殺場景其特點是高並發、讀多寫少、資源爭用, 每一個點都需要根據其業務場景選擇適合的解決方案, 如使用快取解決頻繁讀取的問題、使用隊列解決資料庫性能瓶頸等.
對於搶紅包業務來說, 預分配和實時分配都是行之有效的方案, 各有優劣, 具體選擇哪種, 還是要看業務需求.
© 2019, 朴瑞卿.
[版權聲明] 本文發佈於朴瑞卿的部落格, 允許非商業用途轉載, 但轉載必須保留原作者朴瑞卿 及鏈接:https://blog.piaoruiqing.com. 如有授權方面的協商或合作, 請聯繫郵箱: [email protected].