Redis 分布式锁
传统setnx 方法(先 status = setnx(key ,valiue) 判断status 是否为 1 ,如果为1 表示占用锁成功 ,占用锁成功后 再设置失效时间 ) 存在的问题 setnx 和expire 不是原子操作,如果expire 失败了 就没有失效时间,当前线程会一直占用锁不释放。所以需要对 setnx 和 expire 操作保证原子性 。
从 Redis 2.6.12 版本开始,SET命令的行为可以通过一系列参数来修改:
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX keymillisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
redisCluster.set(key,"1","nx","ex",100); //如果 key 不存在的话 往缓存中存 value =1 存100秒 ,设置成功以后 返回“OK”
这种方法可以保证获取锁的原子性 不会因为某个线程获取锁时redis 挂掉等导致的长期占有锁的情况。目前笔者的部门采用的就是此种方法。但是此方法仍然存在一定的问题。
假设一个线程持有锁的有效时间是10秒钟,但是由于业务复杂 在持有锁期间线程1 的任务没有执行完毕 ,此时锁已经开放,线程2 可以持有锁,当线程1 任务执行完毕以后会进行del(key)释放锁的操作,但是此时释放的锁确实线程2 持有的锁,此时就会有问题。
所以可以在释放锁(del)之前进行判断一下 持有锁的线程是不是 当前的线程。
加锁的时候 StringthreadId = Thread.currentThread().getId() set(key,threaId,nx,ex,100);
释放锁的时候 if(threadId .equals(redisClient.get(key))){del(key)}
但是由于 判断的操作不是原子性操作,仍然有上述的原子性问题,所以要想实现验证和删除过程的原子性,可以使用Lua脚本来实现。这样就能保证验证和删除过程的正确性了。
如果担心锁要失效的时候 任务没有执行完毕,我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。
事实上这类琐最大的缺点就是它加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:
在Redis的master节点上拿到了锁;
但是这个加锁的key还没有同步到slave节点;
master故障,发生故障转移,slave节点升级为master节点;
导致锁丢失。
正因为如此,Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock
Redlock原理后续会继续讨论。
网友评论