MySQL还能实现分布式锁?

前言之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨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@Slf4jpublic class MySQLOrderService {@Resourceprivate KdOrderMApper orderMapper;@Resourceprivate KdOrderItemMapper orderItemMapper;@Resourceprivate KdProductMapper productMapper;@Resourceprivate DistributeLockMapper distributeLockMapper;//购买商品idprivate int purchaseProductId = 100100;//购买商品数量private int purchaseProductNum = 1;@Transactional(propagation = Propagation.REQUIRED)publicInteger 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-distributereturn order.getId();}}


推荐阅读