Redis锁 - RedLock
redis提供了一个分布式锁的规范算法 Redlock java版本:Redisson :https://github.com/redisson/redisson锁的特点:独享(互斥)、无死锁(持有锁的客户端崩溃或者网络分裂,锁仍然可以使用)、容错(大部分节点活着可用)第一个阶段:redis设置锁,就是创建一个key,然后针对整个key设置过期时间,执行完,删除这个key,这里面有个问题,redi
RedLock 由来原理
redis提供了一个分布式锁的规范算法 Redlock java版本:Redisson :https://github.com/redisson/redisson
锁的特点:独享(互斥)、无死锁(持有锁的客户端崩溃或者网络分裂,锁仍然可以使用)、容错(大部分节点活着可用)
第一个阶段:
redis设置锁,就是创建一个key,然后针对整个key设置过期时间,执行完,删除这个key,这里面有个问题,redis挂了怎么办?增加一个从节点?redis的主从是异步的,如果主挂了,还是有问题。(客户端A从master获取到锁,然后master挂了,slave成为主,这时候客户端B获取到锁,锁失效)
第二个阶段:
使用单个redis实例上锁 这种方式保证redis在大多数场景下总是可用的
使用redis的特性setnx
SET resource_name my_random_value NX PX 30000
只有key存在才能设置成功,多个客户端竞争的key resource_name ,每个客户端的my_random_value不能相同,30000 过期时长,PX过期属性
之后执行完业务逻辑,删除我自己创建的锁,使用lua脚本,执行是原子性,这里要校验一下存的值value和当前客户端存放的值相同,才能删除,防止删了别人的锁,这个my_random_value一定要是唯一的,
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
Redlock算法
在redis的分布式环境中,假设有N个Master节点,彼此之间是独立的,通过同时操作多台加锁解决,现在redis需要加锁,就在这N个节点执行加锁或者释放锁的时间,具体操作和上面单个redis处理一样,具体执行的步骤如下:
1.获取当前的时间,以毫秒为单位;
2.依次或者并发尝试,调用N个实例上使用锁,使用相同的key设置锁,客户端与redis连接超时时间要小于锁的过期时间,不然有的节点锁已经过期了,你还没拿到锁,还在等待是没有意义的。如果锁在规定的时间之内没有获取到锁,就尝试另外一台redis;
3.客户端使用时间为当前时间,减去开始获取锁的时间,就是获取锁的使用时间t1,当且仅当这里的大多数redis获取到锁(至少是N/2+1,过半),同时t1<过期时间,获取锁成功。
4.获取锁成功,那么业务执行的时间为过期时间减t1,就是真正执行的时间;(这里要注意,锁的失效时间和业务执行时间之间的关系)
5.如果获取锁失败(获取锁超时或者获取锁的数量少于N/2+1),客户端需要释放锁。
失败重试
多个客户端,同时获取锁都获取到部分,没有过半,则生成一个随机等待的时间,之后再次执行获取锁,还有获取锁失败的客户端要尽快的释放锁,让其它的尽快获取到,多个客户端获取锁,要并发的请求,获取的速度足够快失败的次数就少一点。
释放锁
向所有节点发送释放指令,不用关心之前时候获得过锁,使用lua脚本
如果我们使用redis锁,aof落盘方式使用fsync=always,提高锁的安全性,但是降低了性能
这里还有一个问题,如果redis挂了,然后重启,这时候可能clientA获取到了锁,clientB也获取到了,这时候我们使用redis的延迟重启(超过这个TTL),保证A锁过期之后再重启成功,可以避免这个问题,但是会有性能上的问题。
产生死锁场景
如果一个业务机器获取到redis锁,在执行的过程中如果业务机器挂了,这时候就会导致锁一直在这个业务机器没有释放,导致死锁。
解决死锁方法 可重入锁
redisson,为了解决这个问题设置一个lockWatchdogTimeout参数,看门狗检查超时时间,默认为30秒这个可以通过Config.lockWatchdogTimeout 参数进行设置,这个作用保证在客户端执行业务过程中,不会因为业务还没有执行完成就过期。如果业务没有执行完,锁快到期不断的进行续期。
另外Redisson还通过加锁的方法,提供leaseTime的参数来指定加锁的时间,如果超过这个时间之后便自动解开。
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
RLock
对象完全符合Java的Lock规范。也就是说只有拥有锁的进程才能解锁,其他进程解锁则会抛出IllegalMonitorStateException
错误。但是如果遇到需要其他进程也能解锁的情况,请使用分布式信号量Semaphore 对象.
红锁(RedLock)
如果因为单个redis节点宕机或者主从的问题导致线程安全的问题,使用redlock解决
基于Redis的Redisson红锁RedissonRedLock
对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock
对象关联为一个红锁,每个RLock
对象实例可以来自于不同的Redisson实例。
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();
大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime
的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
附录:
REDIS distlock -- Redis中国用户组(CRUG)
更多推荐










所有评论(0)