1. 程式人生 > 實用技巧 >“樂觀鎖”解決高併發下的冪等性問題(附java實測視訊教程)

“樂觀鎖”解決高併發下的冪等性問題(附java實測視訊教程)

什麼是冪等性?

冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。在程式設計中.一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。冪等函式,或冪等方法,是指可以使用相同引數重複執行,並能獲得相同結果的函式。這些函式不會影響系統狀態,也不用擔心重複執行會對系統造成改變。例如,“getUsername()和setTrue()”函式就是一個冪等函式. 更復雜的操作冪等保證是利用唯一交易號(流水號)實現. 我的理解:冪等就是一個操作,不論執行多少次,產生的效果和返回的結果都是一樣的 。

高併發下的冪等性問題

這裡以兩個例項來看下高併發下的冪等性問題;

一,購票例項

購票實現流程如下:

step1:查詢是否有票,有票的話,繼續下一步,否則提示無票,結束;

step2:從使用者賬戶扣除票款;

step3:餘票減一操作;

這裡的話,正常情況沒問題,但是比如使用者連續多點了幾次,或者網路問題導致的再或者多人同時購買的時候的併發情況下,step1步驟會有兩個或者多個執行緒同時進入,這時候判斷都是有票的,然後繼續進入step2,step3,這時候,就可能會出現餘票負數,多賣的情況;

二,充值例項

充值實現流程如下:

step1:使用者輸入充值金額,請求後端業務系統;

step2:後端生成訂單,訂單狀態是未支付,然後再請求第三方支付介面;

step3:使用者端確認支付;

step4:第三方支付通過我方提供的回撥介面非同步通知支付結果;

具體step4 demo程式碼如下:

System.out.println("查詢訂單");
Order order = orderMapper.getByOrderId(orderId); // 根據訂單id獲取訂單
if(order.getStatus()==0){ // 假如是未支付狀態
  System.out.println("未支付狀態");
  order.setStatus(1); // 設定支付成功狀態
  System.out.println("更新支付狀態...");
  orderMapper.update(order); 
// 更新支付狀態 System.out.println("賬戶充值..."); userAccountMapper.addAmount(order.getAmount(),userAccount.getUserId()); // 賬戶充值 System.out.println("充值完畢..."); return true; }else{ // 已經支付成功,訂單已處理 System.out.println("發現訂單已處理"); return true; }

這個第四步是有缺陷的,假如第三方支付系統問題或者網路問題,有多個執行緒同時執行進入

Order order = orderMapper.getByOrderId(orderId);


根據訂單id查詢訂單資訊,發現status狀態都是未支付,所以都進入if裡面,這時候就出現了賬戶重複充值的情況;


冪等性問題總結

只要更新資料是依賴讀取的資料作為基礎條件的,當遇到高併發的時候,就可能會出現冪等性問題;
又比如在更新資料不依賴查詢的資料的就不會有問題,例如修改使用者的名稱,多人同時修改,結果並不依賴於之前的使用者名稱字,這就不會有併發更新問題。

冪等性問題解決方案

關於冪等性問題的解決方案,業界提供了很多解決方案,如單機系統的Java 同步鎖,樂觀鎖,悲觀鎖,分散式鎖,唯一性索引,token機制防止頁面重複提交等,每種方案各有利弊;不過主流的話,還是樂觀鎖和分散式鎖這兩個方案;

Java同步鎖方案

我們可以使用synchronized同步鎖,把查詢狀態的程式碼和更新的程式碼放一個同步鎖內,這樣同一時刻只能有一個執行緒進入執行,等執行完其他執行緒才能進入,這樣能解決冪等性問題,但是假如同步塊裡面的業務程式碼執行時間比較長,這樣會嚴重影響使用者體驗,和系統的吞吐量。所以不是最佳方案;

悲觀鎖方案

悲觀鎖(Pessimistic Lock),顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會block直到它拿到鎖。

悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。

Java synchronized 就屬於悲觀鎖的一種實現,每次執行緒要修改資料時都先獲得鎖,保證同一時刻只有一個執行緒能操作資料,其他執行緒則會被block。

資料庫的悲觀鎖通過 for update 實現的;

select * from t_order where orderId=#{orderId} for update

悲觀鎖使用時一般伴隨事務一起使用,資料鎖定時間可能會很長,影響使用者體驗和系統吞吐量,所以一般也不採用。

樂觀鎖方案

樂觀鎖(Optimistic Lock),顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在提交更新的時候會判斷一下在此期間別人有沒有去更新這個資料。樂觀鎖適用於讀多寫少的應用場景,這樣可以提高吞吐量。

樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。

樂觀鎖一般來說有以下2種方式:

1. 使用資料版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式。何謂資料版本?即為資料增加一個版本標識,一般是通過為資料庫表增加一個數字型別的 “version” 欄位來實現。當讀取資料時,將version欄位的值一同讀出,資料每更新一次,對此version值加一。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的version值進行比對,如果資料庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期資料。

2. 使用時間戳(timestamp)。樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個欄位,名稱無所謂,欄位型別使用時間戳(timestamp), 和上面的version類似,也是在更新提交的時候檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本衝突。

樂觀鎖方案在不影響系統性能的情況下,解決了高併發冪等性問題,所以被得到廣泛使用。唯一的缺點就是對程式碼具有入侵性。

分散式鎖

對於分散式系統,多個系統獨立執行,所以同步鎖肯定是不行的;對於分散式系統,可以用樂觀鎖或者分散式鎖來解決冪等性問題;

具體方案有:

1. 基於快取(Redis等)實現分散式鎖;

2. 基於Zookeeper實現分散式鎖;

(備註:下期我們會提供具體實現方案的視訊教程,感謝關注)

基於“樂觀鎖”解決冪等性視訊教程

感謝各位兄弟姐妹關注,鋒哥為了大夥能更深刻的掌握“樂觀鎖”解決冪等性問題,專門錄製了一期視訊教程。主要以賬戶充值為例,採用IDEA開發工具,資料庫Mysql5.7,demo基於springboot+mybatis架構,用JMeter測試工具模擬,高併發,來測試出冪等性問題,也就賬戶被重複充值的場景。然後通過基於狀態機version欄位的樂觀鎖解決方案,解決冪等性問題,也同時附有完整程式碼。

紙上得來終覺淺,絕知此事要躬行

需要多實戰練習和思考。

B站視訊教程線上地址

------------------------------------------------------------------------------------------------------------------------------

作者:java1234_小鋒

出處:https://www.cnblogs.com/java688/p/13446074.html

版權:本站使用「CC BY 4.0」創作共享協議,轉載請在文章明顯位置註明作者及出處。

------------------------------------------------------------------------------------------------------------------------------