在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题
但是Java中的锁,只能保证在同一个JVM进程内中执行。
如果在分布式集群环境下,为了保证共享资源的正确性,就需要用到分布式锁了
分布式锁,是一种思想,它的实现方式有很多
一般实现分布式锁有以下几个方式:
- MySql
- Zookeeper
- Redis
分布式锁需要实现的功能
- 加锁
- 解锁
- 锁超时
redis分布式锁的实现方式
- setnx
- redLock
- redisson
1.setnx
SET key value NX PX 5000
给key设置一个值,实际上就给redis加上了锁,为了避免死锁,给key设置过期时间,释放锁只需要删除key
NX 代表只在键不存在时,才对键进行设置操作
PX 5000 设置键的过期时间为5000毫秒
因为引入了超时时间来避免死锁,同时也引出了其它两个问题
- 问题1:当业务处理的时间超过过期时间后,A和B都会进入临界区
- 问题2:进程A会删除进程B设置的锁
问题1解决方案: 使用延长锁时效的策略
延长锁时效的方案如下:假设锁超时时间是 30 秒,此时程序需要每隔一段时间去扫描一下该锁是否还存在,扫描时间需要小于超时时间,通常可以设置为超时时间的 1/3,在这里也就是 10 秒扫描一次。如果锁还存在,则重置其超时时间恢复到 30 秒。通过这种方案,只要业务还没有处理完成,锁就会一直有效;而当业务一旦处理完成,程序也会马上删除该锁。
问题2解决方案:
在用setnx的时候,key虽然是主要作用,但是value也不能闲着,在创建锁时为其指定一个唯一的标识,可以用UUID这种随机数。
String uuid = xxxx;
// 伪代码,具体实现看项目中用的连接工具
set Test uuid NX PX 3000
try{
// biz handle....
} finally {
// unlock
if(uuid.equals(redisTool.get('Test')){
redisTool.del('Test');
}
}
当解锁的时候,先获取value判断是否是当前进程加的锁,再去删除
但是在finally代码块中,get和del并非原子操作,还是有进程安全问题,那么删除锁的正确姿势之一,就是使用lua脚本
2.RedLock
想要在集群模式下实现分布式锁,Redis 提供了一种称为 RedLock 的方案
红锁算法认为,只要(N/2) + 1个节点加锁成功,那么就认为获取了锁, 解锁时将所有实例解锁。
流程为:
- 顺序向五个节点请求加锁
- 根据一定的超时时间来推断是不是跳过该节点
- 三个节点加锁成功并且花费时间小于锁的有效期(超时时间 - 花费时间)
- 认定加锁成功
3.Redisson
Redisson 是 Redis 的 Java 客户端,它提供了各种的 Redis 分布式锁的实现
Redisson实现分布式锁(1)---原理 - 雨点的名字 - 博客园 (cnblogs.com)
文章参考(juejin.cn/post/684490…)