温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么在mysql中实现一个分布式锁

发布时间:2021-01-04 16:22:20 来源:亿速云 阅读:298 作者:Leah 栏目:开发技术

本篇文章给大家分享的是有关怎么在mysql中实现一个分布式锁,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

分布式锁

什么是分布式锁?

那么什么是分布式锁呢,在说分布式锁之前我们看到单体应用锁的特点就是在一个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中实现一个分布式锁

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

  • 优点:简单方便、易于理解、易于操作。

  • 缺点:并发量大的时候对数据库的压力会比较大。

  • 建议:作为锁的数据库和业务数据库分开。

以上就是怎么在mysql中实现一个分布式锁,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI