小猿最近很苦惱:明明加了分布式鎖,為什么并發還是會出問題呢?
故事從接到需求開始說起。
小猿前一陣接到一個小任務,里面有一個功能對應的場景如下:
注:實際業務場景比較復雜,已做簡化。
小猿略作思考,就抓住了關鍵點:余額操作——要注意事務,多實例——要注意并發。
小猿的原始代碼如下:
@Override@Lock(key = "#accountNo")@Transactional(rollbackFor = Exception.class)public void updateBalance(String accountNo, AmountOperateParam param) { // do something}
可以看到,這個方法上通過注解方式加了分布式鎖和事務,鎖的 key 是 accountNo,也就是賬戶業務主鍵。
自測和測試也沒發現啥問題,就高高興興發完版回家了。
第二天一早,就接到少量用戶反饋,說自己的賬戶余額不對了。
小猿的第一反應是:我這塊自測和測試都沒問題,其它功能導致的吧?本地又是一通自測,也沒有復現問題。但謹慎起見,還是往代碼里加了一些日志,來確認是不是自己的方法引發的。
當又有用戶反饋時,小猿根據日志的情況確認了:還真是自己方法的問題,對同一個賬戶的余額操作,多個并發請求會同時執行到方法體里面。
也就是說……分布式鎖沒鎖住?
冥思苦想了好久,又在本地做了大量的測試,終于讓小猿找到了問題所在:AOP 執行順序問題。
小猿設計的時序:
但實際的時序:
也就是說期望是這樣的執行順序:
但實際的執行順序:
分布式鎖和事務,都是通過 AOP 來實現的,而 AOP 的執行順序是根據切面的優先級來的,而小猿的分布式鎖切面的優先級比事務切面的優先級低,所以就出現了上面的時序問題。
于是通過給分布式鎖的切面指定 Order 的方式,讓它的優先級高于事務切面(注:Order 值越小,執行優先級越高),驗證完沒問題后,就又高高興興地更新完版本,修復好歷史問題數據后回家了。
誰知道第二天一早,還是有極少量的用戶反饋賬戶余額不對的問題。
這次小猿就有點懵了,為什么還會出現這種情況呢?
經過一番艱苦卓絕的排查,終于找到了問題所在:事務嵌套。
從前文中的示例代碼中可以看到,小猿的方法上加了事務注解 @Transactional(rollbackFor = Exception.class) 里,沒有指定事務的傳播行為,默認是 Propagation.REQUIRED,也就是說如果當前沒有事務,就新建一個事務;如果當前有事務,就加入到當前事務中。
小猿自己寫的代碼里沒有在事務方法里嵌套調用這個方法的情況,但是同事寫的代碼里有,這樣就會導致前文的時序問題再次發生。
找到問題就好辦了,小猿將自己的方法上的事務傳播行為改成了 Propagation.REQUIRES_NEW,也就是說如果當前沒有事務,就新建一個事務;如果當前有事務,就將當前事務掛起,新建一個事務。
這次更新完版本后,小猿就再也沒有收到用戶反饋了,終于可以安心回家睡覺了。
在日常的開發過程中,如果涉及到并發和事務,一定要多留幾個心眼,考慮周全,確認以下要點是否都正確實現:
本文鏈接:http://www.tebozhan.com/showinfo-26-11218-0.html后端|一個分布式鎖「失效」的案例分析
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com