併發減庫存

不要亂摸發表於2018-06-04

秒殺的場景有很多,比如:搶購、搶票、搶紅包等等。總之,就是在極短時間內有大量的請求。

我們都知道,這種系統設計的大方向就是限流,即通過層層過濾,最終只讓相對較少的請求進入到核心業務處理層。

這裡不談秒殺設計,不談使用佇列等使請求序列化,就談下怎麼用鎖來保證資料正確,就是已經到減庫存那一步了,在這一步中如果保證不超賣。

用佇列的話,可以是Java自動的佇列,也可以用Redis的LPUSH RPOP

重點是扣減庫存

我理解,主要的方式是加鎖。加鎖有兩個層面:一個是程式層面,另一個是資料庫層面。

分散式鎖

這種場景下應該很少有人用Java自帶的鎖(比如:synchronized、Lock)吧,因為它們只在同一個JVM內有效,如果你的應用部署了多臺的話,應該用分散式鎖。

關於Redis分散式鎖,可以看我之前的一篇《基於Redis的分散式鎖的簡單實現

其實,這裡加分散式鎖就是將多執行緒請求轉成單執行緒請求,因為每次只有一個執行緒獲得鎖並執行,其餘都被阻塞了。

這裡有一點需要注意,就是當你應用了事務的話可能會存在問題,請看下面的程式碼

可能有人會這樣寫,第一眼看起來挺好的,沒問題啊,但仔細實踐證明是由問題的。

我們知道,mysql預設的事務隔離級別是REPEATABLE-READ

關於事務隔離級別這塊兒,可參考《mysql事務隔離級別

在這種隔離級別下,同一個事務中多次讀取,返回的資料是一樣的

同時,Spring宣告式事務預設的傳播特性REQUIRED

Spring宣告式事務是Spring AOP最好的例子,Spring是通過AOP代理的方式來實現事務的,也就是說在呼叫reduceStock()方法的之前就已經開啟了事務。

那麼,在併發情況下可能會存在這樣的情況,假設執行緒T1和T2都執行到這裡,於是它們都開啟了事務S1和S2,T1先執行,T2後執行,

由於T2執行的時候事務已經建立了,根據隔離級別,這個時候事務S2讀取不到S1已提交的資料,於是就會出現T1和T2讀取到的值是一樣的,即T2讀取的是T1更新前的庫存資料。

關於這一點,大家可以自己寫個程式碼測試一下,下面是一段參考:

鑑於這種情況呢,可以將庫存放到Redis中,我們直接讀寫Redis,這樣可以避免受資料庫事務的影響,當然這也會帶來新的問題,不再討論。

資料庫樂觀鎖

CAS(compare and swap)比較並交換

在Java中,一個執行緒想修改某個變數的值,那麼第一步是將變數的值從主記憶體中讀取到自己工作記憶體中,然後修改,最後寫回主記憶體。這個過程可以歸結為:讀取——修改——寫入,在寫回記憶體的時候可能當前記憶體中那個值已經發生了變化,這個時候如果繼續寫則會覆蓋別人的資料,只有當記憶體中的那個值和它修改之前讀到的那個值一樣,才可以寫入。這個跟資料庫是一樣的。Java中通過Unsafe中compareAndSwapObject這樣的方法類實現的,它直接呼叫CPU指令。

資料庫中也有CAS,樂觀鎖就是一種CAS

經典的樂觀鎖實現:

資料增加一個版本標識,一般是通過為資料庫表增加一個數字型別的 “version” 欄位來實現。當讀取資料時,將version欄位的值一同讀出,資料每更新一次,對此version值加一。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的version值進行比對,如果資料庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期資料。

更新的時候帶上版本號,只有當前版本號與更新之前查詢時的版本一致,才會更新

ABA問題

這裡順便多提一句,CAS中的ABA問題

假設,原先的值是A,執行緒-1讀取到的值是A,想把它改成D,但是在此期間,有可能其他執行緒已經多次修改過這個值,只不過最後當執行緒-1準備將A改成D的時候,它發現恰好還是A,以為沒有人改過,其實這時候的A已經不是原來的A了。

也就是說,儘管修改之前做了比較,當然,仍然會出現如下情況:

產生原因

 ABA問題導致的原因,是CAS過程中只簡單進行了“值”的校驗,有些情況下,“值”雖然相同,卻已經不是原來的資料了。

優化方向

CAS不能只比對“值”,還必須確保的是原來的資料,才能修改成功。

常見實踐

“版本號”的比對,一個資料一個版本,版本變化,即使值相同,也不應該修改成功。

不僅要關注值,還要關注是不是原來的物件

基於“值”的CAS樂觀鎖,可能導致ABA問題。CAS樂觀鎖,必須保證修改時的“此資料”就是“彼資料”,應該由“值”比對,優化為“版本號”比對。

 

參考

https://www.sohu.com/a/150900817_178889

https://blog.csdn.net/zhjunjun93/article/details/78560700

https://blog.csdn.net/rexct392358928/article/details/52230737

 

相關文章