优惠劵系统库存设计浅谈
優惠劵系統活動庫存一般分為:總庫存和日庫存。在一個用戶來領取優惠劵時,需要判斷當前剩余總庫存和日庫存是否充足,如果充足則進行庫存扣減,否則提示用戶領取失敗。總庫存和日庫存的扣減是一個原子操作,要么都成功,要么都失敗。我們知道數據庫事務滿足"ACID"特性,因此可以將這兩個操作放到一個事務中進行。
原始階段庫存設計
在最初階段,系統用戶數量較少,優惠劵領取請求流量不大。我們可以將一個活動的總庫存和日庫存放到同一個MySQL數據庫中。一個活動的總庫存對應一條記錄,一個活動的日庫存在活動期間每天都有一條日庫存記錄。在用戶來領取優惠劵時,我們開啟一個事務,首先扣減總庫存,其次扣減日庫存,如果任一步驟失敗,則回滾事務,前端提示用戶領取優惠劵失敗。
考慮到系統數據庫模塊的可擴展性,我們采用“分庫分表”的方式進行數據的“sharding”,按照具體的業務ID來分庫,例如我們可以按照活動號來分片我們的數據,這樣同一個活動的總庫存和日庫存都落在一個數據庫中,可以做成一個事務。出于性能考慮我們不采用分布式事務。
這種系統設計的優點是:實現簡單,庫存具有強一致性(由事務保證);但是其缺點也很明顯:事務開啟時,MySQL會給總庫存和日庫存加行鎖(InnoDB存儲引擎),行鎖具有排他性,在一個事務提交之前其他事務都必須等待。因此,雖然在領劵時我們系統是開啟多線程方式并發處理請求,但是在更新數據庫時所有的請求還是“串行化”操作。假設,我們更新總庫存和日庫存這兩個操作一次耗時10ms,那么系統針對每個活動的領劵請求“TPS”最高也只有100,這對于一些“熱點”活動高并發領取來說是無法忍受的。
Redis內存數據庫庫存扣減
Redis是一個高性能的分布式緩存系統,它既可以用作緩存,也可以用作內存數據庫。它的特點是性能極高,因為操作都是純內存操作。單機最高能夠達到10W的QPS,對于一般的系統,這個已經能夠滿足要求。"Every coin has two sides",雖然Redis的性能極高,但是它也有缺點,例如redis的事務不能像MySQL一樣能夠保證事務的"ACID"特性,Redis 的事務保證了 ACID 中的一致性(C)和隔離性(I),但并不保證原子性(A)和持久性(D)。這樣就可能產生問題,例如在扣減庫存時,首先扣減總庫存,再扣減日庫存。假設總庫存扣減成功,日庫存扣減失敗。Redis沒有回滾機制,這樣即使事務失敗了,也沒法回滾總庫存,從而產生問題。
Redis高可用
Redis支持集群部署,Redis 集群有16384個槽,通過計算key的CRC16校驗和再對16384取模得到key落在哪個slot上(CRC16(key) % 16384)。 當單個Redis節點撐不住時可以考慮用Redis集群的方式來實現庫存扣減。主從復制,master節點和slave節點之間用Sentinel做故障轉移。
Redis同步扣減庫存,異步同步到數據庫方案
雖然Redis提供了持久化方案(RDB,AOF),但是對于一些重要的業務數據,Redis本身的持久化方案可能還不夠,我們需要將Redis中記錄的庫存數據異步同步到數據庫中。在極端情況下Redis掛了,還有數據庫作為"憑證"。 因此,根據業務需要,可以考慮開啟一個后臺任務,定時地將Redis中記錄的庫存數據同步到數據庫中。
總結
以上是生活随笔為你收集整理的优惠劵系统库存设计浅谈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java8 - 新的时间日期API示例
- 下一篇: 哈哈哈哈哈改