優惠劵系統活動庫存通常分爲:總庫存和日庫存。在一個用戶來領取優惠劵時,須要判斷當前剩餘總庫存和日庫存是否充足,若是充足則進行庫存扣減,不然提示用戶領取失敗。總庫存和日庫存的扣減是一個原子操做,要麼都成功,要麼都失敗。咱們知道數據庫事務知足"ACID"特性,所以能夠將這兩個操做放到一個事務中進行。html
在最初階段,系統用戶數量較少,優惠劵領取請求流量不大。咱們能夠將一個活動的總庫存和日庫存放到同一個MySQL數據庫中。一個活動的總庫存對應一條記錄,一個活動的日庫存在活動期間天天都有一條日庫存記錄。在用戶來領取優惠劵時,咱們開啓一個事務,首先扣減總庫存,其次扣減日庫存,若是任一步驟失敗,則回滾事務,前端提示用戶領取優惠劵失敗。前端
考慮到系統數據庫模塊的可擴展性,咱們採用「分庫分表」的方式進行數據的「sharding」,按照具體的業務ID來分庫,例如咱們能夠按照活動號來分片咱們的數據,這樣同一個活動的總庫存和日庫存都落在一個數據庫中,能夠作成一個事務。出於性能考慮咱們不採用分佈式事務。redis
這種系統設計的優勢是:實現簡單,庫存具備強一致性(由事務保證);可是其缺點也很明顯:事務開啓時,MySQL會給總庫存和日庫存加行鎖(InnoDB存儲引擎),行鎖具備排他性,在一個事務提交以前其餘事務都必須等待。所以,雖然在領劵時咱們系統是開啓多線程方式併發處理請求,可是在更新數據庫時全部的請求仍是「串行化」操做。假設,咱們更新總庫存和日庫存這兩個操做一次耗時10ms,那麼系統針對每一個活動的領劵請求「TPS」最高也只有100,這對於一些「熱點」活動高併發領取來講是沒法忍受的。數據庫
Redis是一個高性能的分佈式緩存系統,它既能夠用做緩存,也能夠用做內存數據庫。它的特色是性能極高,由於操做都是純內存操做。單機最高可以達到10W的QPS,對於通常的系統,這個已經可以知足要求。"Every coin has two sides",雖然Redis的性能極高,可是它也有缺點,例如redis的事務不能像MySQL同樣可以保證事務的"ACID"特性,Redis 的事務保證了 ACID 中的一致性(C)和隔離性(I),但並不保證原子性(A)和持久性(D)。這樣就可能產生問題,例如在扣減庫存時,首先扣減總庫存,再扣減日庫存。假設總庫存扣減成功,日庫存扣減失敗。Redis沒有回滾機制,這樣即便事務失敗了,也無法回滾總庫存,從而產生問題。緩存
Redis支持集羣部署,Redis 集羣有16384個槽,經過計算key的CRC16校驗和再對16384取模獲得key落在哪一個slot上(CRC16(key) % 16384)。 當單個Redis節點撐不住時能夠考慮用Redis集羣的方式來實現庫存扣減。主從複製,master節點和slave節點之間用Sentinel作故障轉移。多線程
雖然Redis提供了持久化方案(RDB,AOF),可是對於一些重要的業務數據,Redis自己的持久化方案可能還不夠,咱們須要將Redis中記錄的庫存數據異步同步到數據庫中。在極端狀況下Redis掛了,還有數據庫做爲"憑證"。 所以,根據業務須要,能夠考慮開啓一個後臺任務,定時地將Redis中記錄的庫存數據同步到數據庫中。併發