一般在日常開發中經常會遇到打折促銷,秒殺活動,就如拼多多最近的4999搶券買愛瘋11促銷活動,畢竟誰的錢也不是大風刮來的,有秒殺有促銷必定帶來大量用戶,而這類活動往往支撐着公司重要營銷策略,所以保證系統在高並發下不出異常非常關鍵,這其中棘手的便是如何在高並發下高效的處理庫存數據。今天就來聊聊高並發下庫存加減那些事兒。
首先我們要明確重要的一點是減庫存是需要順序的,而需要順序就意味着不能有並發加減庫存的操作,為了實現順序,一般做法都是將多線程強行變為單線程實現同步操作或者所說的順序,將多線程變為單線程方案普遍做法便是使用鎖或者借助隊列。另一方面由於大型互聯網應用面向大量用戶所以都是大型分布式加集群作為最基礎的架構,而由於架構原因,往常所使用的lock或者Synchronized進程鎖關鍵字失去了意義(只能鎖住當前Web程序代碼塊,但無法鎖住集群中其他Web程序)。此時我們便要借助分布式鎖或者MQ組件來達到跨進程跨主機的單線程效果。
接下來我們以ABC下單減庫為例說明分布式下的減庫存場景
ABC同時發起庫存減1的請求
服務器接收到三個減庫存操作,利用分布式鎖鎖住了減庫存的邏輯,每次只限一個請求操作.對A請求進行庫存減1操作后,再對B進行操作,one by one 以此類推。
目前減庫存操作運行很好,不會發生超賣情況,老板再也不擔心程序員敗家了。但是以上減庫存的邏輯有個很大的問題便是由於強行將多線程請求變為單線程,不可避免的導致排隊的發生,這樣會發生什么情況呢?
越后進分布式鎖或者隊列的請求他需要的響應時間越久因為他的響應時間是前面所有請求的響應時間之和。當然有人會說增加配置或者在redis中減庫存再利用rabbitmq將結果同步到數據庫中,由於操作內存中的數據讓減庫存操作響應加快,這的確對單次的減庫存有效,但是隨着並發提高,單次減庫存響應時間的優化必將遇到瓶頸。依然沒有解決高並發下所有人必須強行排隊導致的問題。那有沒有那種又順序執行又能相對的並行加減庫存操作呢?
減庫存必定是順序排隊的,這毋庸置疑,但是有沒有辦法可以加快這個排隊呢,答案是有的!
只有將同步減庫存邏輯變為異步才能從根本解決排隊問題。但是有人會說這與庫存操作的邏輯(同步順序排隊)沖突。
其實這里所說的異步是相對的,什么意思呢?
首先全局庫存是必須順序操作的,但是如果我們把庫存分割成N塊,每一塊內部是順序的,但是每一塊彼此之間又是異步的。這樣就很好的解決了庫存順序執行的邏輯又減輕了排隊的影響。有人會問這里是如何減輕的呢?首先來給減庫存算一筆響應時間的賬:
假設每個減庫存操作的響應時間優化到50毫秒,並發2000,按照常規做法加全局鎖那第2000個人的響應時間便是前面1999個用戶的響應時間加他自己的50毫秒之和為100秒。100秒的響應可能用戶早就心里默默詛咒你了。而且這已經是非常理想化的單次響應時間了。如果有人說可以優化到2毫秒就不會超時了。。麻煩帶上鍵盤去微博杠吧。。
如果使用第二種方案假設三個用戶請求減庫存操作,完全可以讓三個請求進三個不同的鎖去扣減各自的庫存數,此時三人沒有排隊可以保證他們同時減庫存,而又不影響庫存總數的准確性,因為三個請求操作的是各自鎖所維護的庫存數。隨着業務增長,庫存總數的分割可以不斷細分直到縮短響應時間到合理范圍,而這個庫存總數的分割很好的保證了不會遇到瓶頸。但是由於這種業務架構的設計,導致業務不得不變得復雜,可以看到我們在進入分布式鎖之前有一個稱為庫存總數協調器的模塊,這個模塊是用來做什么的呢?
首先我們把庫存分割成多塊后解決的首要問題便是如何讓請求均勻的依次進入每一個分布式鎖中進而操作當前鎖所負責的庫存數。
庫存協調器的邏輯完全看各位自己業務模型來決定,你可以用雪花算法均勻分布也可使用ip或者用戶標識取余去覆蓋到每一個鎖,總之實現方式看業務情況來決定,當然了很大幾率會出現有的庫存塊內的庫存總數消耗完了但有的還剩余,所以庫存協調器一定要考慮到這類情況及時將庫存較多的庫存塊內的庫存數分散給其他庫存塊,以達到多線程減庫存的效果。
從示例圖中可以看到引入了rabbitmq,他在當前整個業務架構中的作用主要是每一個分布式鎖處理完當前庫存塊的庫存后要將當前加減的數量丟給消息隊列,由消費端慢慢消化這些操作到數據庫。
其實解決高並發業務只要你遵循讓一個變成多個的思路,很多都有解決辦法等着你。