黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目
总时长 42:48:00 共175P
此文章包含第56p-第p63的内容
文章目录
分布式锁介绍
不使用分布式锁的情况
使用分布式锁的情况
分布式锁的满足条件
这里一些可以满足也可以不满足的功能,如: 是否满足可重用性、是公平锁还是非公平锁、是阻塞的还是非阻塞的等
基于redis的分布式锁
获取锁
只有第一个成功了
释放锁
如果服务挂了,这里无法释放锁,就会产生死锁
这时候我们需要添加过期时间来阻止此类情况发生
这里要确保setnx 锁 和加过期时间保持原子性,要不然杠添加了锁,还没加过期时间就宕机了就完了,
所以我们使用一个命令来执行
SET 锁 值(线程名) NX EX 10
10秒内无法添加
过期之后又可以添加了
编写代码
创建一个接口
定义一个类 实现这个接口
定义锁的名称 前缀 等信息
定义一个构造函数 让其传入这两个值
调用redisTemplate的setnx方法 即 setIfAbsent()方法
担心产生null 这里使用Boolean.TRUE.equals方法 只有true时返回true 其他时候是false
也可以使用hutool的方法实现
获取锁
释放锁
测试
原来的代码
修改后的代码
创建锁对象 使用的name我们是 业务:用户ID 这样锁的范围很小
因为这是自己写的工具类,需要实现复用,如果要注入就不能在别的地方用了,所以我们使用传入stringTedisTemolate的方式
也可以设置用户id+优惠卷id拼接
开始测试,测试多个jvm的情况
用postman发送两个请求
第一个是lock为true
第二个是lock为false
redis存到的数据
分布式锁误删问题
如果业务阻塞时间太久 超过了过期时间 直接释放锁了
这时候线程1把线程2的锁释放了 这时候线程2还没完成,线程3就进来了
这样就导致线程安全问题
应该在释放锁的时候判断一下是不是自己这个线程的锁 别把别人的锁给开了
改造代码
直接使用线程id的时候 集群情况下会出现冲突 这里我们加上uuid
ps:这里的uuid使用static final声明,这个uuid前缀一直不变,这样相当于这个uuid代表了本次启动后 这个jvm的一个唯一值
修改释放代码
开始测试
tomcat1 获取锁 然后阻塞住 然后手动删除模拟过期
让tomcat2执行,也获取到了锁
让tomcat1释放锁 ,可以看到两个id不一样
tomcat2释放锁
锁被删除
原子性
在一些极端条件下 还是存在问题
如果jvm垃圾回收的时间特别长 阻塞的时间超过了线程锁的过期时间
所以判断锁和释放锁的两个命令必须是原子性操作才可以
使用lua命令
可以使用lua脚本保持命令的原子性
可以进入网站学习lua脚本命令
不带参数的写法
后面的0代表本次没有参数进来 是个写死的脚本
带参数的写法
切记 lua的数组下标是从1开始的不是0
这里的1代表key的数量
业务流程
用vscode新建一个文件 然后 选择编程语言 这样就可以选择lua语言 这样有高亮提示(idea也有lua插件)
判断当前的锁id是否相同,相同就删除 的lua脚本
简化脚本
编写脚本java代码
我们创建一个lua脚本文件 (创文件.lua后缀,idea会提示安装插件)
这里注意,虽然返回值只是0和1,但是也要用Long类型接值。脚本执行不支持接受Integer类型
使用 ClassPathResource()方法找到classpath下的文件
我们的文件放在 resources文件夹下 就直接就是classpath
代码如下· 因为我们放到了static块下 因此这个类加载的时候就会去访问unlock.lua,这个脚本就被初始化了
Collections.singletonList单元素数组
这时已经将代码变成了一行 就不会出现原子性问题了