mysql乐观锁一定比悲观锁性能高(mysql居然还能实现分布式锁的方法)
mysql乐观锁一定比悲观锁性能高
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日志,
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 causejava.lang.Exception: 商品100100仅剩0件,无法购买
at com.kd.distribute.service.MySQLOrderService.createOrder(MySQLOrderService.java:61) ~[classes/:na]
很明显第二个请求由于没有库存,导致最终购买失败的情况,当然这个场景也是符合我们正常的业务场景的。最终我们数据库的情况是这样的:
很明显,我们到此数据库的库存和订单数量也都正确了。到此我们基于数据库的分布式锁实战演示完成,下面我们来归纳一下如果使用这种锁,有哪些优点以及缺点。
- 优点:简单方便、易于理解、易于操作。
- 缺点:并发量大的时候对数据库的压力会比较大。
- 建议:作为锁的数据库和业务数据库分开。
写在最后
对于上述数据库分布式锁,其实在我们的日常开发中用的也是比较少的。基于redis以及zk的锁倒是用的比较多一些,本来老猫想把redis锁以及zk锁放在这一篇中一起分享掉,但是再写在同一篇上面的话,篇幅就显得过长了,因此本篇就和大家分享这一种分布式锁。源码大家可以在老猫的github中下载到。地址是:https://github.com/maoba/kd-distribute
到此这篇关于mysql居然还能实现分布式锁的方法的文章就介绍到这了,更多相关mysql 分布式锁内容请搜索开心学习网以前的文章或继续浏览下面的相关文章希望大家以后多多支持开心学习网!
原文链接:https://juejin.cn/post/6913456598094626823
- mysql高可用集群(MySQL之高可用集群部署及故障切换实现)
- linuxmysql安装教程5.7.25学习(linux mysql5.5升级至mysql5.7的步骤与踩到的坑)
- oracle如何用脚本文件创建表空间(MySQL版oracle下scott用户建表语句实例)
- mysql存储引擎是什么(详解mysql中的存储引擎)
- mysqlbinlog怎么分析(MySQL中使用binlog时格式该如何选择)
- mysql创建存储过程的代码(MySQL修改存储过程的详细步骤)
- win10下安装mysql8.0.23 及 “服务没有响应控制功能”问题解决办法(win10下安装mysql8.0.23 及 “服务没有响应控制功能”问题解决办法)
- mysql存储过程和函数(MySQL存储过程的查询命令介绍)
- mysql 死锁查询(MySQL slave 延迟一列 外键检查和自增加锁)
- oracle和mysql查询所有表(Oracle、MySQL和SqlServe三种数据库分页查询语句的区别介绍)
- mysql内部有4种常见日志(MySQL 慢日志相关知识总结)
- mysql的主从复制怎么做(MySQL主从复制原理以及需要注意的地方)
- mysql索引的机制(Mysql索引选择以及优化详解)
- mysql什么是慢查询(MySQL慢查询的坑)
- mysql并发查询优化(详解MySQL 联合查询优化机制)
- mysql支持存储表情(MySQL如何插入Emoji表情)
- 专访 《紧急公关》折射现实生态 主演黄晓明 理性看待 向往美好(紧急公关折射现实生态)
- 庆余年剧组重聚王牌5,宋轶神秘消失,肖战出现一秒抢了李纯风头(庆余年剧组重聚王牌5)
- 巴厘岛旅游攻略(巴厘岛旅游攻略7天多少钱)
- 文莱旅游攻略(文莱旅游攻略介绍)
- 马来西亚旅游攻略(马来西亚旅游攻略自由行攻略)
- 缅甸旅游攻略(缅甸旅游攻略必去景点推荐)
热门推荐
- mysql多行数据之和(详解MySQL的数据行和行溢出机制)
- python进度条怎么实现(Python小进度条显示代码)
- docker如何简化部署(Docker使用Portainer搭建可视化界面的方法)
- mysql冷热数据分离方案(MySQL中使用流式查询避免数据OOM)
- opencv轮廓模糊识别(Opencv+Python实现图像运动模糊和高斯模糊的示例)
- php如何将数组清空(PHP实现数组向任意位置插入,删除,替换数据操作示例)
- html代码简单特效(HTML实现代码雨源码及效果示例)
- 宝塔面板防火墙是自动开的吗(宝塔面板开启隐藏的 waf 防火墙的方法)
- dede模板栏目调用(织梦DEDECMS5.7栏目列表页分页URL优化列表页重复的解决方案)
- VS不生成.vhost.exe和.pdb文件
排行榜
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9