mysql居然还能实现分布式锁的方法


本文整理自网络,侵删。

前言

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

单体应用锁的局限性

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

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

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

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

分布式锁

什么是分布式锁?

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

设计思路

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

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

实现方式

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

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

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

基于数据库的分布式锁

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

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

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

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

我们将上一篇中的代码改造一下抽取出一个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日志,

阅读剩余部分

相关阅读 >>

怎么把文本导入mysql

mysql与php的基础与应用专题之创建数据库表

mysql在windows环境下的解压安装及备份还原详解

mysql中如何设置外键约束?

mysql8.0.20安装教程及其安装问题详细教程

mysql 创建三张关系表实操

mysql5.7.20 安装配置方法图文教程(mac)

mysql执行外部sql脚本文件的命令

mysql怎么加入一个数据库?

如何批量修改mysql表字符集

更多相关阅读请进入《mysql》频道 >>


数据库系统概念 第6版
书籍

数据库系统概念 第6版

机械工业出版社

本书主要讲述了数据模型、基于对象的数据库和XML、数据存储和查询、事务管理、体系结构等方面的内容。



打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...