1,同步扣庫存
在訂單生成的時候就扣去庫存,存在一些問題:
- 會造成訂單被取消,實際庫存扣了,但是沒有支付,也就造成了少買的情況
- 即時扣庫存,並發性太差
2,異步扣庫存
對於電商網站,比如某東,會在訂單支付成功后,有一個出庫的過程,有可能出庫成功,也有可能出庫失敗。
庫存有兩部分:一個緩存Redis層, 一個數據庫MySQL層
2.1當客服新增了5個庫存,那么,緩存Redis和數據庫MySQL層都需要增加5個庫存,這個使用分布式事務的最終一致性來滿足要么全家,要么全部不加。
2.2在訂單生成的時候,需要先扣庫存,先扣除Redis,生成支付訂單,這時不扣除MySQL的庫存
2.3當Redis庫存扣完了,就無法下單了,下單失敗,在外層把請求擋住
2.4用戶支付訂單,訂單支付成功后,返回我的訂單,有一個出庫過程
2.5出庫是一個MQ異步解耦的消息隊列,這個過程是扣除MySQL庫存,如果扣除成功,那么出庫成功,完成訂單的整個流程,進入發貨狀態;如果扣除庫存失敗,則進行一些列操作1)訂單狀態改成取消2)返還Redis庫存3)退款
3,Redis庫存和MySQL庫存
支付前是預扣,扣除Redis庫存
支付后是真正扣除,扣MySQL庫存
在一些極端情況下數據不一致
3.1如果Redis庫存和MySQL庫存一直,沒問題
3.2如果Redis庫存比MySQL庫存多,超賣的訂單出庫失敗
3.3如果Redis庫存比MySQL庫存少,少買,有貨沒賣出去
這樣總體不會出問題,MySQL數據庫層,保證庫存不會再真正有問題
4,如何檢測Redis和MySQL庫存不一致
4.1暴力方法,很容易想到,找一個低峰期,比如凌晨2點,周期性強行覆蓋,但是也可能在這個時候出現一個訂單,出庫的過程中,扣除了MySQL庫存,但是沒有扣除Redis庫存(我是這樣理解的:這時有一個訂單,通過Redis的庫存扣除后,然后支付進入出庫狀態,這時已經取出MySQL的庫存數要同步到Redis中,緊接着MySQL庫存扣除了,Redis的數據還是扣除前的數據,所以就會數據不一致)
這個就是數據庫同步緩存的更新機制方面的問題
屬於一致性的邏輯設計的問題
緩存數=數據庫庫存數-待扣數
當然這里面也還有其它的方案,以及考慮到一致性的要求高低,可以使用簡單或復雜的方案 就看系統復雜度了,越是大系統就要拆得越細
比如待扣數又可以放到一個隊列里面,或者緩存里面,同時有計數,直接讀計數就行
比如放到mongo,已支付待出庫的數量,一般也不會很大,count一下,也不會損失多少
所以一般系統都不能完全保障數據鏈不出錯,但一定要有補償,就是出錯了可以糾錯 要保障不出錯的代價顯然太大
同步是有一套刷新機制,可以定時,也可以通過MQ,或者監控不一至同步等等。。。 也叫做保障緩存數據的新鮮度
一般不會太長時間,半小時,幾分鍾都有可能,不同場景需求不一樣
5.買火車票的12306,晚上的時間都不能買票,這個時間估計是在同步庫存
,將數據庫庫存
同步到redis庫存
中, 但是買火車票之類,在訂單生成前,必須扣除實際庫存,也就是要扣除mysql的庫存,
因為買火車票和購物不一樣,購物可以付款后出庫,但是買票這種,支付前就必須出庫,因此,要將出庫過程提前, 只有出庫成功,才能生成訂單,同樣要引入redis庫存
5.1先扣緩存中的庫存,扣除成功后,然后才可以去扣mysql中的庫存
5.2如果扣除緩存中的庫存失敗,就會擋在外面,返回庫存不足,這些請求不會穿刺到mysql中,擋住了大多數的請求壓力。
5.3redis庫存會和mysql庫存不一致,極端情況下是肯定有的,需要進行庫存同步
5.3.1當緩存庫存比數據庫庫存多,那么就會出現,查詢有票,但是就無法下單,下單的時候就說庫存不足, 這樣也不會超賣,當redis的庫存多的那部分扣完了,就可以把請求全部當在外面了。 對於12306,有時候,查詢的時候有票,但是下單的時候返回庫存不足,然后重新查詢發現還是有庫存, 這種情況應該就是redis中庫存和mysql中庫存不一致造成的。
5.3.2當緩存庫存比數據庫緩存少,那么不會出問題,只會出現有票,但是沒有出售的情況,等完成庫存同步一下, 明天又准確了。
5.3.3當然,mysql扣除庫存的部分,還需要在前面加入隊列緩沖,避免請求過多,讓應用程序或數據庫崩潰。