成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

MySQL樂觀鎖在分布式場(chǎng)景下的實(shí)踐

Alan / 1656人閱讀

摘要:那我們?nèi)绾蝸韺?shí)現(xiàn)樂觀鎖呢一般采用以下方式使用版本號(hào)機(jī)制來實(shí)現(xiàn),這是樂觀鎖最常用的實(shí)現(xiàn)方式。從輸出的結(jié)果可以看出用戶的減庫存操作成功了,商品庫存成功減去而用戶提交減庫存操作時(shí),數(shù)據(jù)版本號(hào)已經(jīng)改變,所以數(shù)據(jù)變更失敗。

MySQL樂觀鎖在分布式場(chǎng)景下的實(shí)踐 背景

在電商購物的場(chǎng)景下,當(dāng)我們點(diǎn)擊購物時(shí),后端服務(wù)就會(huì)對(duì)相應(yīng)的商品進(jìn)行減庫存操作。在單實(shí)例部署的情況,我們可以簡(jiǎn)單地使用JVM提供的鎖機(jī)制對(duì)減庫存操作進(jìn)行加鎖,防止多個(gè)用戶同時(shí)點(diǎn)擊購買后導(dǎo)致的庫存不一致問題。

但在實(shí)踐中,為了提高系統(tǒng)的可用性,我們一般都會(huì)進(jìn)行多實(shí)例部署。而不同實(shí)例有各自的JVM,被負(fù)載均衡到不同實(shí)例上的用戶請(qǐng)求不能通過JVM的鎖機(jī)制實(shí)現(xiàn)互斥。

因此,為了保證在分布式場(chǎng)景下的數(shù)據(jù)一致性,我們一般有兩種實(shí)踐方式:一、使用MySQL樂觀鎖;二、使用分布式鎖。

本文主要介紹MySQL樂觀鎖,關(guān)于分布式鎖我在下一篇博客中介紹。

樂觀鎖簡(jiǎn)介

樂觀鎖(Optimistic Locking)與悲觀鎖相對(duì)應(yīng),我們?cè)谑褂脴酚^鎖時(shí)會(huì)假設(shè)數(shù)據(jù)在極大多數(shù)情況下不會(huì)形成沖突,因此只有在數(shù)據(jù)提交的時(shí)候,才會(huì)對(duì)數(shù)據(jù)是否產(chǎn)生沖突進(jìn)行檢驗(yàn)。如果產(chǎn)生數(shù)據(jù)沖突了,則返回錯(cuò)誤信息,進(jìn)行相應(yīng)的處理。

那我們?nèi)绾蝸韺?shí)現(xiàn)樂觀鎖呢?一般采用以下方式:使用版本號(hào)(version)機(jī)制來實(shí)現(xiàn),這是樂觀鎖最常用的實(shí)現(xiàn)方式。

版本號(hào)

那什么是版本號(hào)呢?版本號(hào)就是為數(shù)據(jù)添加一個(gè)版本標(biāo)志,通常我會(huì)為數(shù)據(jù)庫中的表添加一個(gè)int類型的"version"字段。當(dāng)我們將數(shù)據(jù)讀出時(shí),我們會(huì)將version字段一并讀出;當(dāng)數(shù)據(jù)進(jìn)行更新時(shí),會(huì)對(duì)這條數(shù)據(jù)的version值加1。當(dāng)我們提交數(shù)據(jù)的時(shí)候,會(huì)判斷數(shù)據(jù)庫中的當(dāng)前版本號(hào)和第一次取數(shù)據(jù)時(shí)的版本號(hào)是否一致,如果兩個(gè)版本號(hào)相等,則更新,否則就認(rèn)為數(shù)據(jù)過期,返回錯(cuò)誤信息。我們可以用下圖來說明問題:

如圖所示,如果更新操作如第一個(gè)圖中一樣順序執(zhí)行,則數(shù)據(jù)的版本號(hào)會(huì)依次遞增,不會(huì)有沖突出現(xiàn)。但是像第二個(gè)圖中一樣,不同的用戶操作讀取到數(shù)據(jù)的同一個(gè)版本,再分別對(duì)數(shù)據(jù)進(jìn)行更新操作,則用戶的A的更新操作可以成功,用戶B更新時(shí),數(shù)據(jù)的版本號(hào)已經(jīng)變化,所以更新失敗。

代碼實(shí)踐

我們對(duì)某個(gè)商品減庫存時(shí),具體操作分為以下3個(gè)步驟:

查詢出商品的具體信息

根據(jù)具體的減庫存數(shù)量,生成相應(yīng)的更新對(duì)象

修改商品的庫存數(shù)量

為了使用MySQL的樂觀鎖,我們需要為商品表goods加一個(gè)版本號(hào)字段version,具體的表結(jié)構(gòu)如下:

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;

    /**
     * 庫存數(shù)量
     */
    private Integer remainingNumber;

    /**
     * 版本號(hào)
     */
    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);     //模擬并發(fā)情況,不同的用戶讀取到同一個(gè)數(shù)據(jù)版本
        } 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測(cè)試類

@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的請(qǐng)求
            }
        });
        thread.start();
        goodsService.updateGoodCAS(id, 2);            //用戶2的請(qǐng)求

        System.out.println(goodsService.selectGoodById(id));
    }
}

輸出結(jié)果:

Goods{id=1, name="手機(jī)", remainingNumber=10, version=9}
Goods{id=1, name="手機(jī)", remainingNumber=10, version=9}
success
fail
Goods{id=1, name="手機(jī)", remainingNumber=8, version=10}

代碼說明:

在updateGoodCASTest()的測(cè)試方法中,用戶1和用戶2同時(shí)查出id=1的商品的同一個(gè)版本信息,然后分別對(duì)商品進(jìn)行庫存減1和減2的操作。從輸出的結(jié)果可以看出用戶2的減庫存操作成功了,商品庫存成功減去2;而用戶1提交減庫存操作時(shí),數(shù)據(jù)版本號(hào)已經(jīng)改變,所以數(shù)據(jù)變更失敗。

這樣,我們就可以通過MySQL的樂觀鎖機(jī)制保證在分布式場(chǎng)景下的數(shù)據(jù)一致性。

以上。

原文鏈接

https://segmentfault.com/a/11...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66944.html

相關(guān)文章

  • 并發(fā)同步控制

    摘要:并發(fā)同步控制遇到并發(fā)時(shí),我們避免不了要談并發(fā)控制。它會(huì)阻塞其它的線程執(zhí)行,如果當(dāng)前線程一直持有的監(jiān)控鎖,就會(huì)把其它線程一直阻塞下去。如果此時(shí)線程和線程同時(shí)進(jìn)入方法,用一段語言描述方法的執(zhí)行過程,可能是這樣子。 并發(fā)同步控制 遇到并發(fā)時(shí),我們避免不了要談并發(fā)控制。在Java語言中,我們談并發(fā)時(shí),要談到Object的監(jiān)控鎖。在MySQL的數(shù)據(jù)庫并發(fā)中,我們也要談到mysql的鎖機(jī)制。 這樣...

    graf 評(píng)論0 收藏0
  • 冪等的實(shí)現(xiàn)方案

    摘要:冪等實(shí)現(xiàn)方案冪等性不能脫離業(yè)務(wù)來討論。在不同的需求場(chǎng)景下,實(shí)現(xiàn)冪等的思路和方案也會(huì)不同,一般有如下通用方案多版本并發(fā)控制這是樂觀鎖的一種實(shí)現(xiàn),用于在數(shù)據(jù)庫并發(fā)訪問時(shí)的情況。去重表這是利用數(shù)據(jù)庫表單的特性來實(shí)現(xiàn)冪等。 背景 在軟件系統(tǒng)的開發(fā)過程中,我們可能有如下需求: 創(chuàng)建業(yè)務(wù)訂單,一次業(yè)務(wù)請(qǐng)求只能創(chuàng)建一個(gè); 單個(gè)訂單請(qǐng)求調(diào)用支付接口,當(dāng)遇到網(wǎng)絡(luò)或系統(tǒng)故障請(qǐng)求重發(fā),也應(yīng)該只支付一次; ...

    NikoManiac 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<