服務報價 | 域名主機 | 網絡營銷 | 軟件工具| [加入收藏]
?熱線電話: 130-5800-8007
當前位置: 主頁 > 開發教程 > mysql教程 >

MySQL樂觀鎖在分布式場景下的實踐

時間:2017-04-04 00:16來源:未知 作者:最模板 點擊:
MySQL樂觀鎖在分布式場景下的實踐 背景 在電商購物的場景下,當我們點擊購物時,后端服務就會對相應的商品進行減庫存操作。在單實例部署的情況,我們可以簡單地使用JVM提供的鎖機

MySQL樂觀鎖在分布式場景下的實踐

背景

在電商購物的場景下,當我們點擊購物時,后端服務就會對相應的商品進行減庫存操作。在單實例部署的情況,我們可以簡單地使用JVM提供的鎖機制對減庫存操作進行加鎖,防止多個用戶同時點擊購買后導致的庫存不一致問題。

但在實踐中,為了提高系統的可用性,我們一般都會進行多實例部署。而不同實例有各自的JVM,被負載均衡到不同實例上的用戶請求不能通過JVM的鎖機制實現互斥。

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

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

樂觀鎖簡介

樂觀鎖(Optimistic Locking)與悲觀鎖相對應,我們在使用樂觀鎖時會假設數據在極大多數情況下不會形成沖突,因此只有在數據提交的時候,才會對數據是否產生沖突進行檢驗。如果產生數據沖突了,則返回錯誤信息,進行相應的處理。

那我們如何來實現樂觀鎖呢?一般采用以下方式:使用版本號(version)機制來實現,這是樂觀鎖最常用的實現方式。

版本號

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

如圖所示,如果更新操作如第一個圖中一樣順序執行,則數據的版本號會依次遞增,不會有沖突出現。但是像第二個圖中一樣,不同的用戶操作讀取到數據的同一個版本,再分別對數據進行更新操作,則用戶的A的更新操作可以成功,用戶B更新時,數據的版本號已經變化,所以更新失敗。

代碼實踐

我們對某個商品減庫存時,具體操作分為以下3個步驟:

  1. 查詢出商品的具體信息

  2. 根據具體的減庫存數量,生成相應的更新對象

  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如下:

<update id="updateGoodCAS" parameterType="com.ztl.domain.Goods">
        <![CDATA[
          update goods
          set `name`=#{name}, 
          remaining_number=#{remainingNumber}, 
          version=version+1
          where id=#{id} and version=#{version}
        ]]>
    </update>

GoodsMapperTest.java測試類

@RunWith(SpringRunner.class)
@SpringBootTest
public class GoodsMapperTest {

    @Autowired
    private GoodsMapper goodsMapper;

    @Test
    public void updateGoodCASTest() {
        Integer id = 1;
        Goods good1 = goodsMapper.selectGoodById(id);
        Goods good2 = goodsMapper.selectGoodById(id);

        //打印當前商品信息
        System.out.println(good1);
        System.out.println(good2);

        //更新good1
        good1.setRemainingNumber(good1.getRemainingNumber()-1);
        int result1 = goodsMapper.updateGoodCAS(good1);
        System.out.println("減庫存" + (result1 == 1 ? "成功" : "失敗"));

        //更新good2
        good2.setRemainingNumber(good2.getRemainingNumber()-2);
        int result2 = goodsMapper.updateGoodCAS(good2);
        System.out.println("減庫存" + (result2 == 1 ? "成功" : "失敗"));
    }
}

輸出結果:

Goods{id=1, name='手機', remainingNumber=7, version=3}
Goods{id=1, name='手機', remainingNumber=7, version=3}
減庫存成功
減庫存失敗

代碼說明:

在updateGoodCASTest()的測試方法中,我們同時查出id=1的商品的同一個版本信息,然后分別通過good1和good2對象對庫存數量進行修改,第一次通過good1的修改成功了;但第二次修改時,update where 條件中的版本號已經不匹配,所以更新失敗。

這樣,我們就可以通過MySQL的樂觀鎖機制保證在分布式場景下的數據一致性。

(責任編輯:最模板)
頂一下
(0)
0%
踩一下
(0)
0%
------分隔線----------------------------
欄目列表
熱點內容
体彩22选5开奖结果