摘要:那我们如何来实现乐观锁呢一般采用以下方式使用版本号机制来实现,这是乐观锁最常用的实现方式。从输出的结果可以看出用户的减库存操作成功了,商品库存成功减去而用户提交减库存操作时,数据版本号已经改变,所以数据变更失败。
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...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/66944.html
摘要:幂等实现方案幂等性不能脱离业务来讨论。在不同的需求场景下,实现幂等的思路和方案也会不同,一般有如下通用方案多版本并发控制这是乐观锁的一种实现,用于在数据库并发访问时的情况。去重表这是利用数据库表单的特性来实现幂等。 背景 在软件系统的开发过程中,我们可能有如下需求: 创建业务订单,一次业务请求只能创建一个; 单个订单请求调用支付接口,当遇到网络或系统故障请求重发,也应该只支付一次; ...
阅读 3031·2021-11-23 09:51
阅读 1008·2021-09-02 15:21
阅读 2976·2019-08-30 13:56
阅读 1793·2019-08-29 14:12
阅读 659·2019-08-29 13:53
阅读 1596·2019-08-29 11:32
阅读 1261·2019-08-29 11:25
阅读 1457·2019-08-28 17:51