高并发库存秒杀场景,阿里巴巴数据库是这样应对的
簡單庫存場景的數據庫實現
一般來說,從數據庫層面講,庫存業務會分為兩步,第一步是插入一條記錄到扣減明細表inventory_detail,第二步是對庫存扣減表inventory的一條記錄進行扣減,這兩步往往是在一個事務中實現的。
數據庫業務架構圖如下,所有的請求均發往同一個Database。
從上文的架構圖不難看出,所有的商品的庫存信息都存在單一的表和庫里,當商品種類繁多或者業務并發請求暴漲時,單實例的數據庫顯然會成為容量或者性能瓶頸。該數據庫架構一般只是功能性的實現,主要用于微型庫存系統或者測試使用。
高并發庫存系統的數據庫實現
為了解決單實例存在的容量和性能上限問題,阿里巴巴所有的庫存系統在十年前就實現了分庫分表設計,主要通過數據的水平拆分實現不同商品的庫存扣減請求路由到不同的數據庫。基本數據庫架構圖如下
從上圖不難看出,庫存扣減表和扣減明細表一般都使用商品id作為片鍵,這樣可以保證滿足整個系統在高并發扣減請求的同時,同一商品的庫存扣減操作和添加明細操作在同一個事務中實現。如果數據分布和業務請求足夠均勻,理論上經過分庫分表設計后,整個系統的吞吐量將會是線性的增長,主要取決于分實例的數量。
熱點行更新
在電商業務中,商家活動比如秒殺不可避免。秒殺活動會給電商庫存系統帶來巨大的挑戰,尤其體現在數據庫層面。因為往往一個商品id對應于數據庫的一行記錄,所以在DB架構上按照商品維度做了分庫分表也是無效的。而更新這行記錄時必然需要給這行記錄加X鎖。熱點商品的庫存扣減本質上就是熱點行更新的能力,高并發的同行更新會造成嚴重的行鎖等待現象,從而導致數據庫的threads_running和rt飆升,造成雪崩。在當前的官方mysql中,一般單行更新的QPS在500以內,對于熱點商品的秒殺需求,這個量往往是不達標的。
阿里巴巴PolarDB-X數據庫團隊基于以上場景的需求,針對內核優化,引入了先進的水車模型,在識別出熱點SQL后,實現了在內核層面優化處理,相比官方MySQL提高了10倍以上的熱點行扣減能力,廣泛應用于集團電商庫存集群,資金平臺,權益發放平臺等核心數據庫集群。
其主要的核心思想是:針對應用層SQL做輕量化改造,帶上"熱點行SQL"的標簽,當這種SQL進入內核后,在內存中維護一個hash表,將主鍵或唯一鍵相同的請求(一般也就是同一商品id)hash到同一個地方做請求的合并,經過一段時間后(默認100us)統一提交,從而實現了將串行處理變成了批處理,讓每個熱點行更新請求并不需要都去掃描和更新btree。
類似原理,阿里云RDS數據庫團隊同樣在內核層面針對熱點行更新做了大量的優化,核心思路為引入SQL語句的排隊機制,將可能存在行鎖沖突的語句放在一個組內排序,從而減少行鎖沖突帶來的額外系統開銷。Statement Queque和Inventory Hint可以結合使用,不過在事務中,熱點行更新必須是該事務的最后一條記錄,因為commit on success的機制存在,一旦該SQL執行成功就會自動提交或自動回滾。簡單的使用范例如下
begin; insert into inventory_detail(inventory_id,user_id) values(1,1); update /*+ ccl_queue_value(1) commit_on_success rollback_on_fail target_affect_row(1) */ inventory set inventory_count=inventory_count+1 where inventory_id=1更多文檔參考inventory hint statement queue
業務架構優化
冪等性實現
在庫存數據庫系統中,一般都會在更新庫存記錄后,寫入一條庫存扣減明細的流水記錄,用于后續可能存在的查詢需求。舉個例子,在集團的權益發放平臺中,庫存流水記錄主要用于實現庫存扣減的冪等性,即同一個用戶只能領取一次權益。在系統的實際運行過程中,可能因為一些網絡故障等其他原因,當底層數據庫的扣減成功以后并沒有成功返回給用戶時,用戶可能會有重試操作,這時就必須避免庫存記錄的重復扣減情況。所以針對這些情況,應用在設計時會考慮先查詢一遍庫存流水記錄,如果該用戶已經領取過該權益,則不再重復扣減,直接返回。為了實現這種強冪等性的需求,庫存扣減和插入流水就必須在同一個事務中,滿足同時成功或同時失敗。
基于緩存的分桶扣減方案
在更大規模,針對單一商品的超高并發扣減的庫存集群中,可能基于數據庫內核的改造優化還無法滿足業務需求。單一商品的超高并發扣減可能會影響到同一數據庫實例上的其他商品扣減,同一個數據庫實例上也可能存在多個熱點商品造成互相影響,這時就需要考慮在業務和數據庫架構上再做一次升級,我們引入基于緩存的分桶扣減方案。
下圖為該方案數據庫架構圖,基于緩存的分桶扣減方案的主要思路為
- 1、普通的非熱點商品,或者并發度不夠大的熱點商品走強冪等性的分庫分表+數據庫內核改造優化
- 2、超大熱點商品,針對該商品再做多key拆分,先走弱冪等性的緩存扣減,緩存扣減后,異步往DB寫入一條庫存流水記錄,后續再做緩存與數據庫的庫存總量同步
在分桶方案詳細落地實現上,需要考慮的細節問題會多很多,比較重要的有以下幾點
1、分桶管理
為了更通俗和直觀的描述,緩存集群的一個key就對應于于一個"分桶"。要實現一個基于緩存分桶方案的高擴展性的庫存系統,分桶的設計至關重要,比如一個熱點商品應該對應多少個分桶,分桶的數量能否根據當前的業務變化做到彈性的伸縮
- 1、分桶預分配庫存:當分桶初始化后,每個分桶應該保存多少庫存量。不一定在預分配庫存階段將該商品的庫存數量從DB全部分配到緩存中,可能是一種漸進式的分配策略,DB作為庫存總池子
- 2、分桶擴容/縮容:分桶數量的變化,擴縮容操作本質上是調整桶映射管理內的信息,加入或者減少桶,桶信息一旦增加或者減少了,扣減鏈路會秒級感知到,然后將用戶流量引導或者移除出去。從上面的DB架構圖可以看出,比較簡單的實現方式就是根據當前熱點商品的桶數量取模
- 3、桶內庫存數量擴容/縮容:即每個分桶內該商品的庫存數量變化,擴容場景主要用于當該分桶內庫存接近扣減完成時,系統自動去MySQL庫存集群總池子里撈一部分過來放進桶內。縮容場景主要場景在于桶下線后將桶內剩余的庫存回收到庫存總池子中
- 4、合并展示:在基于緩存的分桶設計中,由于同一種熱點商品拆分成了多個key,所以在前端界面展示上同樣會帶來挑戰,需要做庫存的合并
2、超賣問題
一個較為簡單的處理超賣問題的思路是預留一部分庫存,當庫存數量低于之前定義的預留值時,直接返回前端庫存扣減完畢,從而避免造成超賣。
3、碎片問題
在一些類庫存系統的設計中,考慮到系統的兼容性和支持的扣減種類,或許扣減的是商品的庫存數量,或許是紅包的金額(將帶小數的紅包金額轉換成整型數扣減)。所謂碎片問題,舉個例子,假如扣減的是紅包金額,假設紅包金額至少要發1塊錢,換算成整型數也就是100,在多個分桶扣減的情況下,最后部分分桶的剩余庫存值可能低于100,而所有分桶加起來的總額又大于100。如果不做處理,就會造成資損。
應對這種極端場景,系統需要在檢測到存在碎片時,自動將存在碎片的分桶下線納入庫存總池子,由DB總池子再分出少量的緩存key來進行扣減,多次循環直到不存在碎片為止。或者針對出現這種情況后,由于庫存總量已經基本扣減完畢,在納入DB總池子后直接在DB側扣減。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的高并发库存秒杀场景,阿里巴巴数据库是这样应对的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云EDAS 3.0重磅发布,无侵入构
- 下一篇: 一个半月快速、低成本上云,云数据库专属集