Py学习  »  DATABASE

MySQL乐观锁在分布式场景下的实践

仁者在掘金 • 5 年前 • 299 次点击  

MySQL乐观锁在分布式场景下的实践

我的头条号www.toutiao.com/c/user/5444…

在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作。在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不一致问题。

但在实践中,为了提高系统的可用性,我们一般都会进行多实例部署。而不同实例有各自的JVM,被负载均衡到不同实例上的用户请求不能通过JVM的锁机制实现互斥。

因此,为了保证在分布式场景下的数据一致性,我们一般有两种实践方式:一、使用MySQL乐观锁;二、使用分布式锁。

本文主要介绍MySQL乐观锁,关于分布式锁我在下一篇博客中介绍。

乐观锁简介

乐观锁(Optimistic Locking)与悲观锁相对应,我们在使用乐观锁时会假设数据在极大多数情况下不会形成冲突,因此只有在数据提交的时候,才会对数据是否产生冲突进行检验。如果产生数据冲突了,则返回错误信息,进行相应的处理。

那我们如何来实现乐观锁呢?一般采用以下方式:使用版本号(version)机制来实现,这是乐观锁最常用的实现方式。

版本号

那什么是版本号呢?版本号就是为数据添加一个版本标志,通常我会为数据库中的表添加一个int类型的"version"字段。当我们将数据读出时,我们会将version字段一并读出;当数据进行更新时,会对这条数据的version值加1。当我们提交数据的时候,会判断数据库中的当前版本号和第一次取数据时的版本号是否一致,如果两个版本号相等,则更新,否则就认为数据过期,返回错误信息。

代码实践

我们对某个商品减库存时,具体操作分为以下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代码:

/* 商品名字
    */
   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>

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);  
           }
       });

       thread.start();
       goodsService.updateGoodCAS(id, 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的乐观锁机制保证在分布式场景下的数据一致性。


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/xiDjc5ZkM5
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/12263
 
299 次点击