背景

在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作。在单实例部署的情况,我们可以简单地使用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代码:

 * 商品名字
*/
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); //用户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...

【转】MySQL乐观锁在分布式场景下的实践的更多相关文章

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

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

  2. 浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    浅谈Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景   Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁 ...

  3. MySQL 乐观锁与悲观锁

    悲观锁 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁. 悲观锁: ...

  4. mysql乐观锁总结和实践--转

    原文地址:http://chenzhou123520.iteye.com/blog/1863407 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任 ...

  5. 使用mysql乐观锁解决并发问题

    案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000 ...

  6. mysql乐观锁总结和实践(转)

    原文:mysql乐观锁总结和实践 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的 ...

  7. 使用mysql乐观锁解决并发问题思路

    本文摘自网络,仅供个人学习之用 案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后 ...

  8. 面试官:如何在分布式场景下生成全局唯一 ID?

    在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或者用作事务版本号.分布式链路追踪等等,好的全局唯 ...

  9. 分布式场景下Kafka消息顺序性的思考

    如果业务中,对于kafka发送消息异步消费的场景,在业务上需要实现在消费时实现顺序消费, 利用kafka在partition内消息有序的特点,消息消费时的有序性. 1.在发送消息时,通过指定parti ...

随机推荐

  1. Nginx配置参数中文说明

    #定义Nginx运行的用户和用户组 user www www;   #nginx进程数,建议设置为等于CPU总核心数. worker_processes 8;   #全局错误日志定义类型,[ debu ...

  2. 【笔记】h5 页面唤起电话呼叫

    参考文章:https://www.cnblogs.com/lilin1995/p/5640684.html 最近完成一个公司的官网移动端页面,涉及到了唤起电话这个功能,说实在js 并没有为此提供 ap ...

  3. 关于在jsp中的路径问题

    前言: jsp作为javaweb开发中常见的视图技术,我们平时在开发项目使用的过程中,经常会导入一些静态资源,比如css\js\jpg.png等图片格式的文件,这些文件的路径成了问题,经常会出现索引不 ...

  4. webpack的世界

    本文也是多次学习webapck积累下来的知识点,一直在云笔记里. webpack的原理 webpack构建流程 从启动webpack构建到输出结果经历了一系列过程,它们是: 解析webpack配置参数 ...

  5. Xshell配置SSH秘钥登录

    秘钥生成 生成公钥 工具 -> 新建用户秘钥生成向导 -> 下一步 -> 点击下一步,输入密码: 点击下一步 点击保存为文件,完成. 生成私钥 工具 -> 用户秘钥管理者 选中 ...

  6. MySQL事务处理案例演示

    <?php $mysqli=new mysqli("localhost", "root", "123456", "xsphp ...

  7. paping使用来测试联通&网站由于tcp协议导致的无法通信问题超时问题

    1. 使用paping来测试连通性 Linux 平台: : wget http://www.updateweb.cn/softwares/paping_1.5.5_x86-64_linux.tar.g ...

  8. Gitbucket—快速建立自己的Github

    GitBucket是一个用Scala语言编写的类似Github的应用,界面非常相似.它非常容易安装–容易到你只需要把它的war文件扔到tomcat中,然后启动tomcat就直接可以访问了.或者直接ja ...

  9. CSS布局(五) 网页布局方式

    网页实质是块与块之间的位置,块挨着块,块嵌套块,块叠着块. 三种关系:相邻,嵌套,重叠. 下面介绍网页布局的常用几种方式 1.一列布局: 一般都是固定的宽高,设置margin : 0 auto来水平居 ...

  10. Linux常用命令(精选)

    chmod -R 777 文件夹名       // -R表示递归给文件及文件夹内文件更改权限,r(4),w(2),x(1),chmod -a+rwx / chmod -u+w -g+r -o +x ...