Redis分布式缓存锁

1.Redis分布式锁

用的Redis来实现分布式锁最简单的方式是在实例里创建一个键值,创建出来的键值有一个超时时间,所以每个锁最终会被释放,当一个客户端想要释放锁时候,客户端只要删除这个键值就可以。

利用redis的脚本编写申请和释放锁代码比利用WATCH / MULTI / EXEC编写的代码更加简洁,减少了业务服务器客户服务器之间的交互,在高并发情况下redis的脚本编写代码比WATCH / MULTI / EXEC性能上高了一倍以上,以下为Redis的脚本

获取锁

if redis.call('exists',KEYS[1])==false then
    return redis.call('setex',KEYS[1],unpack(ARGV))
end

释放锁

if redis.call('get',KEYS[1])==ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return -1
end

这个分布式锁存在如下的问题 
1.如果redis服务器宕机,整个系统都将处于不可用状态,如果使用了redis集群又会引入新的问题,redis的主和奴隶节点之间同步数据需要时间,看如下的一个例子:

  1. 客户端A在主节点上拿到了锁a_source_lock
  2. 从在节点还没来得及同步a_source_lock时主节点宕机于是主切换到从
  3. 客户端乙向新的主节点成功申请锁,于是两个客户端同时拥有了a_source_lock

2。SETNX是一个耗时操作,因为它需要查询这个键是否存在,就算redis的的百万的QPS,在高并发的场景下,这种操作也是有问题的

与的MySQL数据库锁相比,上述缓存锁性能更好,在redis的集群状态下如果容忍主节点宕机时同一资源被多个线程持有的话,这种锁也是一种不错的选择

2. RedisLock算法

在分布式版本的算法里面我们假设有N个Redis Master节点,这些节点都是相互独立的,也就是保证N个节点不会同时宕机。通常将N设为5,算法流程如下

  1. 获取当前的毫秒时间
  2. 轮流用相同的KEY和随机值在Ñ个节点上请求锁,客户端在每个主上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间,目的是为了防止向一个宕机的redis的服务器请求太长时间导致真正锁使用的时间太短。比如锁自动释放的时间是10秒,那么每个节点锁请求超时舌尖应该设置在5〜50ms的。如果一个主不可用,尽快尝试向其它主申请
  3. 客户端计算第二步中获取锁花费的时间,只有当客户端在大多数主节点上获取了锁,并且锁释放的时间大于获取锁的时间,那么这个锁就是有效的
  4. 如果锁获取的时间成功了,那现在锁自动释放的时间就是最初锁释放的时间减去之前获取锁所消耗的时间
  5. 如果锁获取失败,则客户端需要向每个主节点释放锁

2.1失败后重试

当客户端获取锁失败时候,这个客户端应该在一个随机延时后重试,之所以采用随机的延时是为了避免并发情况下,多个客户端同时重试,每个客户端只拿到<(N + 1)/ 2的锁(这样的锁都是失败的锁)。对于失败的锁,线程要及时释放

3.Redis缓存锁总结

redlock解决了第一种redis的分布式锁存在的问题,并且拥有较高的可靠性,但是所有的超时锁包括上述第一种的Redis分布式锁和上篇文章讨论的MySQL的分布式锁都存在一个问题,在FULL GC情况下或者远程服务调用情况下,导致整个应用阻塞,锁因为超时自动释放,但是应用本身未感知,认为自己仍然持有锁,这就出问题了

RedLock复杂性相比第一种Redis的锁更高,主要体现在以下几点

  1. 首先必须部署5个主节点实现RedLock的高可靠性
  2. 同时获取一半以上的Redis服务器的缓存锁,这一步耗时更长
  3. 5个节点,如果某2个节点宕机必须等待超时时间之后才能返回,并且要获取到剩下3个节点的所有的锁,难度也大大增大
  4. 如果出现网络分区,那么可能出现客户端永远也无法获取锁的情况

转自:https://blog.csdn.net/JavaMoo/article/details/78637479 

猜你喜欢

转载自blog.csdn.net/sinat_30126855/article/details/81639841