摘要:那我們如何來實現樂觀鎖呢一般采用以下方式使用版本號機制來實現,這是樂觀鎖最常用的實現方式。從輸出的結果可以看出用戶的減庫存操作成功了,商品庫存成功減去而用戶提交減庫存操作時,數據版本號已經改變,所以數據變更失敗。
MySQL樂觀鎖在分布式場景下的實踐 背景
在電商購物的場景下,當我們點擊購物時,后端服務就會對相應的商品進行減庫存操作。在單實例部署的情況,我們可以簡單地使用JVM提供的鎖機制對減庫存操作進行加鎖,防止多個用戶同時點擊購買后導致的庫存不一致問題。
但在實踐中,為了提高系統的可用性,我們一般都會進行多實例部署。而不同實例有各自的JVM,被負載均衡到不同實例上的用戶請求不能通過JVM的鎖機制實現互斥。
因此,為了保證在分布式場景下的數據一致性,我們一般有兩種實踐方式:一、使用MySQL樂觀鎖;二、使用分布式鎖。
本文主要介紹MySQL樂觀鎖,關于分布式鎖我在下一篇博客中介紹。
樂觀鎖簡介樂觀鎖(Optimistic Locking)與悲觀鎖相對應,我們在使用樂觀鎖時會假設數據在極大多數情況下不會形成沖突,因此只有在數據提交的時候,才會對數據是否產生沖突進行檢驗。如果產生數據沖突了,則返回錯誤信息,進行相應的處理。
那我們如何來實現樂觀鎖呢?一般采用以下方式:使用版本號(version)機制來實現,這是樂觀鎖最常用的實現方式。
那什么是版本號呢?版本號就是為數據添加一個版本標志,通常我會為數據庫中的表添加一個int類型的"version"字段。當我們將數據讀出時,我們會將version字段一并讀出;當數據進行更新時,會對這條數據的version值加1。當我們提交數據的時候,會判斷數據庫中的當前版本號和第一次取數據時的版本號是否一致,如果兩個版本號相等,則更新,否則就認為數據過期,返回錯誤信息。我們可以用下圖來說明問題:
如圖所示,如果更新操作如第一個圖中一樣順序執行,則數據的版本號會依次遞增,不會有沖突出現。但是像第二個圖中一樣,不同的用戶操作讀取到數據的同一個版本,再分別對數據進行更新操作,則用戶的A的更新操作可以成功,用戶B更新時,數據的版本號已經變化,所以更新失敗。
代碼實踐我們對某個商品減庫存時,具體操作分為以下3個步驟:
查詢出商品的具體信息
根據具體的減庫存數量,生成相應的更新對象
修改商品的庫存數量
為了使用MySQL的樂觀鎖,我們需要為商品表goods加一個版本號字段version,具體的表結構如下:
CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL DEFAULT "", `remaining_number` int(11) NOT NULL, `version` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
Goods類的Java代碼:
public class Goods implements Serializable { private static final long serialVersionUID = 0L; private Integer id; /** * 商品名字 */ private String name; /** * 庫存數量 */ private Integer remainingNumber; /** * 版本號 */ private Integer version; @Override public String toString() { return "Goods{" + "id=" + id + ", name="" + name + """ + ", remainingNumber=" + remainingNumber + ", version=" + version + "}"; } }
GoodsMapper.java:
public interface GoodsMapper { Integer updateGoodCAS(Goods good); }
GoodsMapper.xml如下:
GoodsService.java 接口如下:
public interface GoodsService { @Transactional Boolean updateGoodCAS(Integer id, Integer decreaseNum); }
GoodsServiceImpl.java類如下:
@Service public class GoodsServiceImpl implements GoodsService { @Autowired private GoodsMapper goodsMapper; @Override public Boolean updateGoodCAS(Integer id, Integer decreaseNum) { Goods good = goodsMapper.selectGoodById(id); System.out.println(good); try { Thread.sleep(3000); //模擬并發情況,不同的用戶讀取到同一個數據版本 } catch (InterruptedException e) { e.printStackTrace(); } good.setRemainingNumber(good.getRemainingNumber() - decreaseNum); int result = goodsMapper.updateGoodCAS(good); System.out.println(result == 1 ? "success" : "fail"); return result == 1; } }
GoodsServiceImplTest.java測試類
@RunWith(SpringRunner.class) @SpringBootTest public class GoodsServiceImplTest { @Autowired private GoodsService goodsService; @Test public void updateGoodCASTest() { final Integer id = 1; Thread thread = new Thread(new Runnable() { @Override public void run() { goodsService.updateGoodCAS(id, 1); //用戶1的請求 } }); thread.start(); goodsService.updateGoodCAS(id, 2); //用戶2的請求 System.out.println(goodsService.selectGoodById(id)); } }
輸出結果:
Goods{id=1, name="手機", remainingNumber=10, version=9} Goods{id=1, name="手機", remainingNumber=10, version=9} success fail Goods{id=1, name="手機", remainingNumber=8, version=10}
代碼說明:
在updateGoodCASTest()的測試方法中,用戶1和用戶2同時查出id=1的商品的同一個版本信息,然后分別對商品進行庫存減1和減2的操作。從輸出的結果可以看出用戶2的減庫存操作成功了,商品庫存成功減去2;而用戶1提交減庫存操作時,數據版本號已經改變,所以數據變更失敗。
這樣,我們就可以通過MySQL的樂觀鎖機制保證在分布式場景下的數據一致性。
以上。
原文鏈接https://segmentfault.com/a/11...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66944.html
摘要:冪等實現方案冪等性不能脫離業務來討論。在不同的需求場景下,實現冪等的思路和方案也會不同,一般有如下通用方案多版本并發控制這是樂觀鎖的一種實現,用于在數據庫并發訪問時的情況。去重表這是利用數據庫表單的特性來實現冪等。 背景 在軟件系統的開發過程中,我們可能有如下需求: 創建業務訂單,一次業務請求只能創建一個; 單個訂單請求調用支付接口,當遇到網絡或系統故障請求重發,也應該只支付一次; ...
閱讀 3061·2021-11-23 09:51
閱讀 1040·2021-09-02 15:21
閱讀 3005·2019-08-30 13:56
閱讀 1829·2019-08-29 14:12
閱讀 708·2019-08-29 13:53
閱讀 1664·2019-08-29 11:32
閱讀 1325·2019-08-29 11:25
閱讀 1493·2019-08-28 17:51