作者:廢物大師兄
來源:www.cnblogs.com/cjsblog/p/9135118.html
秒殺的場景有很多,比如:搶購、搶票、搶紅包等等。總之,就是在極短時間內有大量的請求。
我們都知道,這種系統設計的大方向就是限流,即通過層層過濾,最終只讓相對較少的請求進入到核心業務處理層。
這里不談秒殺設計,不談使用隊列等使請求串行化,就談下怎么用鎖來保證數據正確,就是已經到減庫存那一步了,在這一步中如果保證不超賣。
用隊列的話,可以是Java自動的隊列,也可以用Redis的LPUSH RPOP
重點是扣減庫存
我理解,主要的方式是加鎖。加鎖有兩個層面:一個是程序層面,另一個是數據庫層面。
分布式鎖
這種場景下應該很少有人用Java自帶的鎖(比如:synchronized、Lock)吧,因為它們只在同一個JVM內有效,如果你的應用部署了多台的話,應該用分布式鎖。
關於Redis分布式鎖,可以閱讀這篇文章:Spring Boot Redis 實現分布式鎖
其實,這里加分布式鎖就是將多線程請求轉成單線程請求,因為每次只有一個線程獲得鎖並執行,其余都被阻塞了。
這里有一點需要注意,就是當你應用了事務的話可能會存在問題,請看下面的代碼
可能有人會這樣寫,第一眼看起來挺好的,沒問題啊,但仔細實踐證明是由問題的。
我們知道,mysql默認的事務隔離級別是REPEATABLE-READ
關於事務隔離級別這塊兒,可在公眾號Java技術棧搜索閱讀。
在這種隔離級別下,同一個事務中多次讀取,返回的數據是一樣的
同時,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
近期熱文推薦:
1.600+ 道 Java面試題及答案整理(2021最新版)
2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式發布,全新顛覆性版本!
覺得不錯,別忘了隨手點贊+轉發哦!