您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

MySQL还能实现分布式锁?

时间:2021-01-05 11:49:16  来源:  作者:

前言

之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨jvm的场景下,我们又该如何去解决并发。

单体应用锁的局限性

在进入实战之前简单和大家粗略聊一下互联网系统中的架构演进。

我看愣了,MySQL还能实现分布式锁?

 

在互联网系统发展之初,消耗资源比较小,用户量也比较小,我们只部署一个Tomcat应用就可以满足需求。一个tomcat我们可以看做是一个jvm的进程,当大量的请求并发到达系统时,所有的请求都落在这唯一的一个tomcat上,如果某些请求方法是需要加锁的,比如上篇文章中提及的秒杀扣减库存的场景,是可以满足需求的。但是随着访问量的增加,一个tomcat难以支撑,这时候我们就需要集群部署tomcat,使用多个tomcat支撑起系统。

在上图中简单演化之后,我们部署两个Tomcat共同支撑系统。当一个请求到达系统的时候,首先会经过Nginx,由nginx作为负载均衡,它会根据自己的负载均衡配置策略将请求转发到其中的一个tomcat上。当大量的请求并发访问的时候,两个tomcat共同承担所有的访问量。这之后我们同样进行秒杀扣减库存的时候,使用单体应用锁,还能满足需求么?

之前我们所加的锁是JDK提供的锁,这种锁在单个jvm下起作用,当存在两个或者多个的时候,大量并发请求分散到不同tomcat,在每个tomcat中都可以防止并发的产生,但是多个tomcat之间,每个Tomcat中获得锁这个请求,又产生了并发。从而扣减库存的问题依旧存在。这就是单体应用锁的局限性。那我们如果解决这个问题呢?接下来就要和大家分享分布式锁了。

分布式锁

什么是分布式锁?

那么什么是分布式锁呢,在说分布式锁之前我们看到单体应用锁的特点就是在一个jvm进行有效,但是无法跨越jvm以及进程。所以我们就可以下一个不那么官方的定义,分布式锁就是可以跨越多个jvm,跨越多个进程的锁,像这样的锁就是分布式锁。

设计思路

我看愣了,MySQL还能实现分布式锁?

 

由于tomcat是JAVA启动的,所以每个tomcat可以看成一个jvm,jvm内部的锁无法跨越多个进程。所以我们实现分布式锁,只能在这些jvm外去寻找,通过其他的组件来实现分布式锁。

上图两个tomcat通过第三方的组件实现跨jvm,跨进程的分布式锁。这就是分布式锁的解决思路。

实现方式

那么目前有哪些第三方组件来实现呢?目前比较流行的有以下几种:

  • 数据库,通过数据库可以实现分布式锁,但是高并发的情况下对数据库的压力比较大,所以很少使用。
  • redis,借助redis可以实现分布式锁,而且redis的java客户端种类很多,所以使用方法也不尽相同。
  • Zookeeper,也可以实现分布式锁,同样zk也有很多java客户端,使用方法也不同。

针对上述实现方式,老猫还是通过具体的代码例子来一一演示。

基于数据库的分布式锁

思路:基于数据库悲观锁去实现分布式锁,用的主要是select ... for update。select ... for update是为了在查询的时候就对查询到的数据进行了加锁处理。当用户进行这种行为操作的时候,其他线程是禁止对这些数据进行修改或者删除操作,必须等待上个线程操作完毕释放之后才能进行操作,从而达到了锁的效果。

实现:我们还是基于电商中超卖的例子和大家分享代码。

咱们还是利用上次单体架构中的超卖的例子和大家分享,针对上次的代码进行改造,我们新键一张表,叫做distribute_lock,这张表的目的主要是为了提供数据库锁,我们来看一下这张表的情况。

我看愣了,MySQL还能实现分布式锁?

 

由于我们这边模拟的是订单超卖的场景,所以在上图中我们有一条订单的锁数据。

我们将上一篇中的代码改造一下抽取出一个controller然后通过postman去请求调用,当然后台是启动两个jvm进行操作,分别是8080端口以及8081端口。完成之后的代码如下:

/**
 * @author kdaddy@163.com
 * @date 2021/1/3 10:48
 * @desc 公众号“程序员老猫”
 */
@Service
@Slf4j
public class MySQLOrderService {
    @Resource
    private KdOrderMApper orderMapper;
    @Resource
    private KdOrderItemMapper orderItemMapper;
    @Resource
    private KdProductMapper productMapper;
    @Resource
    private DistributeLockMapper distributeLockMapper;
    //购买商品id
    private int purchaseProductId = 100100;
    //购买商品数量
    private int purchaseProductNum = 1;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public  Integer createOrder() throws Exception{
        log.info("进入了方法");
        DistributeLock lock = distributeLockMapper.selectDistributeLock("order");
        if(lock == null) throw new Exception("该业务分布式锁未配置");
        log.info("拿到了锁");
        //此处为了手动演示并发,所以我们暂时在这里休眠1分钟
        Thread.sleep(60000);

        KdProduct product = productMapper.selectByPrimaryKey(purchaseProductId);
        if (product==null){
            throw new Exception("购买商品:"+purchaseProductId+"不存在");
        }
        //商品当前库存
        Integer currentCount = product.getCount();
        log.info(Thread.currentThread().getName()+"库存数"+currentCount);
        //校验库存
        if (purchaseProductNum > currentCount){
            throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件,无法购买");
        }

        //在数据库中完成减量操作
        productMapper.updateProductCount(purchaseProductNum,"kd",new Date(),product.getId());
        //生成订单
        ...次数省略,源代码可以到老猫的github下载:https://github.com/maoba/kd-distribute
        return order.getId();
    }
}

SQL的写法如下:

select
   *
    from distribute_lock
    where business_code = #{business_code,jdbcType=VARCHAR}
    for update

以上为主要实现逻辑,关于代码中的注意点:

  • createOrder方法必须要有事务,因为只有在事务存在的情况下才能触发select for update的锁。
  • 代码中必须要对当前锁的存在性进行判断,如果为空的情况下,会报异常

我们来看一下最终运行的效果,先看一下console日志,

8080的console日志情况:

11:49:41  INFO 16360 --- [nio-8080-exec-2] c.k.d.service.MySQLOrderService          : 进入了方法
11:49:41  INFO 16360 --- [nio-8080-exec-2] c.k.d.service.MySQLOrderService          : 拿到了锁

8081的console日志情况:

11:49:48  INFO 17640 --- [nio-8081-exec-2] c.k.d.service.MySQLOrderService          : 进入了方法

通过日志情况,两个不同的jvm,由于第一个到8080的请求优先拿到了锁,所以8081的请求就处于等待锁释放才会去执行,这说明我们的分布式锁生效了。

再看一下完整执行之后的日志情况:

8080的请求:

11:58:01  INFO 15380 --- [nio-8080-exec-1] c.k.d.service.MySQLOrderService          : 进入了方法
11:58:01  INFO 15380 --- [nio-8080-exec-1] c.k.d.service.MySQLOrderService          : 拿到了锁
11:58:07  INFO 15380 --- [nio-8080-exec-1] c.k.d.service.MySQLOrderService          : http-nio-8080-exec-1库存数1

8081的请求:

11:58:03  INFO 16276 --- [nio-8081-exec-1] c.k.d.service.MySQLOrderService          : 进入了方法
11:58:08  INFO 16276 --- [nio-8081-exec-1] c.k.d.service.MySQLOrderService          : 拿到了锁
11:58:14  INFO 16276 --- [nio-8081-exec-1] c.k.d.service.MySQLOrderService          : http-nio-8081-exec-1库存数0
11:58:14 ERROR 16276 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.Exception: 商品100100仅剩0件,无法购买] with root cause

java.lang.Exception: 商品100100仅剩0件,无法购买
	at com.kd.distribute.service.MySQLOrderService.createOrder(MySQLOrderService.java:61) ~[classes/:na]

很明显第二个请求由于没有库存,导致最终购买失败的情况,当然这个场景也是符合我们正常的业务场景的。最终我们数据库的情况是这样的:

我看愣了,MySQL还能实现分布式锁?

 


我看愣了,MySQL还能实现分布式锁?

 

很明显,我们到此数据库的库存和订单数量也都正确了。到此我们基于数据库的分布式锁实战演示完成,下面我们来归纳一下如果使用这种锁,有哪些优点以及缺点。

  • 优点:简单方便、易于理解、易于操作。
  • 缺点:并发量大的时候对数据库的压力会比较大。
  • 建议:作为锁的数据库和业务数据库分开。

写在最后

对于上述数据库分布式锁,其实在我们的日常开发中用的也是比较少的。基于redis以及zk的锁倒是用的比较多一些,本来老猫想把redis锁以及zk锁放在这一篇中一起分享掉,但是再写在同一篇上面的话,篇幅就显得过长了,因此本篇就和大家分享这一种分布式锁。

作者:公众号_程序员老猫

链接:https://juejin.cn/post/6913456598094626823

来源:掘金



Tags:分布式锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
概述以前参加过一个库存系统,由于其业务复杂性,搞了很多个应用来支撑。这样的话一份库存数据就有可能同时有多个应用来修改库存数据。比如说,有定时任务域xx.cron,和SystemA域...【详细内容】
2021-11-05  Tags: 分布式锁  点击:(31)  评论:(0)  加入收藏
作者 | mushishi来源 | urlify.cn/Mry6biredis分布式锁基本原理采用 redis 实现分布式锁,主要是利用其单线程命令执行的特性,一般是 setnx, 只会有一个线程会执行成功,也就是只...【详细内容】
2021-07-07  Tags: 分布式锁  点击:(105)  评论:(0)  加入收藏
前言之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨jvm的场...【详细内容】
2021-01-05  Tags: 分布式锁  点击:(140)  评论:(0)  加入收藏
Nginx+Redis+MQ+DB下秒杀实现原理 Nginx+Redis+MQ+DB下限购实现原理 Nginx+Redis+MQ+DB下亿级流量实现原理 Redis在架构中的意义 分布式微服务是快了还是慢了 高可用和可用...【详细内容】
2020-12-22  Tags: 分布式锁  点击:(364)  评论:(0)  加入收藏
应用场景分布式系统中,面对高并发场景,又对数据一致性有一定要求的情况下,使用分布式锁。例如商城中下单扣库存这种情况。解决方案基于数据库例如:select * from mall_spu where...【详细内容】
2020-11-09  Tags: 分布式锁  点击:(174)  评论:(0)  加入收藏
当下在互联网技术架构中,最流行的莫过于分布式架构了。为什么大家纷纷都采用分布式架构呢? 1、高效低廉,将部署在高性能机的程序分散在多个小型机中部署;2、扩展性强,可随着业务...【详细内容】
2020-11-05  Tags: 分布式锁  点击:(96)  评论:(0)  加入收藏
分布式锁使用场景现在的系统都是集群部署,每个服务都不是单节点的了。比如库存服务,可能部署到3台机器上分别命名为节点1,节点2,节点3。库存服务需要扣减库存,扣减库存肯定需要锁...【详细内容】
2020-09-27  Tags: 分布式锁  点击:(140)  评论:(0)  加入收藏
在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。...【详细内容】
2020-09-01  Tags: 分布式锁  点击:(44)  评论:(0)  加入收藏
分布式锁通常有很多选择,基于 Redis 的,基于 Zookeeper 的,基于数据库等等方案。Redis 用于缓存数据,在项目中都有使用,所以使用 Redis 来做分布式锁的会稍微多些。如果用 Redis...【详细内容】
2020-09-01  Tags: 分布式锁  点击:(102)  评论:(0)  加入收藏
分布式锁三种实现方式:1. 基于数据库实现分布式锁;2. 基于缓存(Redis等)实现分布式锁;3. 基于Zookeeper实现分布式锁; 一, 基于数据库实现分布式锁 1. 悲观锁利用select … w...【详细内容】
2020-07-30  Tags: 分布式锁  点击:(84)  评论:(0)  加入收藏
▌简易百科推荐
作者:雷文霆 爱可生华东交付服务部 DBA 成员,主要负责Mysql故障处理及相关技术支持。爱好看书,电影。座右铭,每一个不曾起舞的日子,都是对生命的辜负。 本文来源:原创投稿 *爱可生...【详细内容】
2021-12-24  爱可生    Tags:MySQL   点击:(6)  评论:(0)  加入收藏
生成间隙(gap)锁、临键(next-key)锁的前提条件 是在 RR 隔离级别下。有关Mysql记录锁、间隙(gap)锁、临键锁(next-key)锁的一些理论知识之前有写过,详细内容可以看这篇文章...【详细内容】
2021-12-14  python数据分析    Tags:MySQL记录锁   点击:(17)  评论:(0)  加入收藏
binlog 基本认识 MySQL的二进制日志可以说是MySQL最重要的日志了,它记录了所有的DDL和DML(除了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二...【详细内容】
2021-12-14  linux上的码农    Tags:mysql   点击:(13)  评论:(0)  加入收藏
为查询优化你的查询 大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查...【详细内容】
2021-12-09  元宇宙iwemeta    Tags:mysql   点击:(15)  评论:(0)  加入收藏
测试的目的和原因,公司有很多程序员,每个程序员对数据库和表结构都有自己的理解。而且每个程序员的理解往往是以效率考虑。既然都是为了效率考虑,那么我就来测试一下究竟哪种使...【详细内容】
2021-12-08  吴彬的分享    Tags:Mysql数据库   点击:(14)  评论:(0)  加入收藏
当你们考虑项目并发的时候,我在部署环境,当你们在纠结使用ArrayList还是LinkedArrayList的时候,我还是在部署环境。所以啊,技术不止境,我在部环境。今天这篇文章缕一下在同一台服...【详细内容】
2021-12-08  秃头码哥    Tags:MySQL数据库   点击:(16)  评论:(0)  加入收藏
对于数据分析来说,MySQL使用最多的是查询,比如对数据进行排序、分组、去重、汇总及字符串匹配等,如果查询的数据涉及多个表,还需要要对表进行连接,本文就来说说MySQL中常用的查询...【详细内容】
2021-12-06  笨鸟学数据分析    Tags:MySQL   点击:(19)  评论:(0)  加入收藏
在学习SQL语句之前,首先需要区分几个概念,我们常说的数据库是指数据库软件,例如MySQL、Oracle、SQL Server等,而本文提到的数据库是指数据库软件中的一个个用于存储数据的容器。...【详细内容】
2021-11-24  笨鸟学数据分析    Tags:SQL语句   点击:(23)  评论:(0)  加入收藏
概述以前参加过一个库存系统,由于其业务复杂性,搞了很多个应用来支撑。这样的话一份库存数据就有可能同时有多个应用来修改库存数据。比如说,有定时任务域xx.cron,和SystemA域...【详细内容】
2021-11-05  Java云海    Tags:分布式锁   点击:(31)  评论:(0)  加入收藏
MySQL的进阶查询 一、 按关键字排序 使用ORDERBY语句来实现排序排序可针对一个或多个字段ASC:升序,默认排序方式 【升序是从小到大】DESC:降序 【降序是从大到小】ORDER BY的...【详细内容】
2021-11-05  Java热点    Tags:SQL语句   点击:(27)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条