首页 > 数据库 >Redis分布式锁问题

Redis分布式锁问题

时间:2023-07-16 14:45:04浏览次数:42  
标签:加锁 Redlock Redis 问题 实例 分布式 节点 客户端

通过SET原子操作来设置key和过期时间

// 加锁
// 如果key不存在,那么设置它的值,否则什么也不做
SETNX lock 1
// 10s后自动过期
EXPIRE lock 10

// 2者合一,一条命令保证原子性执行
SET lock 1 EX 10 NX

问题1:无法评估准确的加锁时间(自动续期)
问题2:客户端1释放了客户端2持有的锁(保存和判断加锁者信息)

RedLock

Redis一般采用主从集群+哨兵的模式部署,好处在于,当主库异常宕机时,哨兵可以实现故障自动切换,把从库提升为主库。

主从切换场景

客户端1在主库上加锁成功
主库异常宕机,SET命令还未同步到从库上(主从复制是异步的)
从库被哨兵提升为新主库,这个锁在新的主库上丢失了

Redlock解决方案

只部署主库,主库部署多个,推荐至少5个实例,它们之间没有任何关系
步骤:
1.客户端先获取当前时间戳T1
2.客户端依次向这5个Redis实例发起加锁请求,且每个请求会设置超时时间(毫秒级,要远小于锁的有效时间),如果某一个实例加锁失败,就立即向下一个Redis实例申请加锁
3.如果客户端从>=3个(大多数)以上Redis实例加锁成功,则再次获取当前时间戳T2,如果T2-T1<锁的过期时间,此时,认为客户端加锁成功,否则认为加锁失败
4.加锁成功,之后向全部节点发起释放锁请求
加锁失败,向全部节点发起释放锁请求

4 个重点
1. 客户端在多个 Redis 实例上申请加锁
2. 必须保证大多数节点加锁成功
3. 大多数节点加锁的总耗时,要小于锁设置的过期时间
4. 释放锁,要向全部节点发起释放锁请求

1) 为什么要在多个实例上加锁?
为了容错,部分实例异常宕机,剩余的实例加锁成功,整个锁服务依旧可用。
2) 为什么大多数加锁成功,才算成功?
多个 Redis 实例一起来用,组成了一个分布式系统,只要大多数节点正常,那么整个系统依旧是可以提供服务的。
3) 为什么步骤3加锁成功后,还要计算加锁的累计耗时?
因为操作的是多个节点,所以即使大多数节点加锁成功,但如果加锁的累计耗时已经超过了锁的过期时间,那此时有些实例上的锁可能已经失效了,这个锁就没有意义了。

对于Redlock的质疑

分布式系统会遇到的三座大山:NPC
N:Network Delay,网络延迟
P:Process Pause,进程暂停(GC)
C:Clock Drift,时钟漂移

Martin 用一个进程暂停(GC)的例子,指出了 Redlock 安全性问题:
1.客户端 1 请求锁定节点 A、B、C、D、E
2.客户端 1 的拿到锁后,进入 GC(时间比较久)
3.所有 Redis 节点上的锁都过期了
4.客户端 2 获取到了 A、B、C、D、E 上的锁
5.客户端 1 GC 结束,认为成功获取锁
客户端 2 也认为获取到了锁,发生「冲突」

GC 可能发生在程序的任意时刻,而且执行时间是不可控的。

当多个 Redis 节点时钟发生问题时,也会导致 Redlock 锁失效。
1. 客户端 1 获取节点 A、B、C 上的锁,但由于网络问题,无法访问 D 和 E
2. 节点 C 上的时钟「向前跳跃」,导致锁到期
3. 客户端 2 获取节点 C、D、E 上的锁,由于网络问题,无法访问 A 和 B
4. 客户端 1 和 2 现在都相信它们持有了锁(冲突)

提出 fecing token 的方案,保证正确性
1. 客户端在获取锁时,锁服务可以提供一个递增的 token
2. 客户端拿着这个 token 去操作共享资源
3. 共享资源可以根据 token 拒绝后来者的请求

反驳质疑

如果在 1-3 发生了网络延迟、进程 GC 等耗时长的异常情况,那在第 3 步 T2 - T1,是可以检测出来的,如果超出了锁设置的过期时间,那这时就认为加锁会失败,之后释放所有节点的锁就好了!
如果发生网络延迟、进程 GC 是在步骤 3 之后,也就是客户端确认拿到了锁,去操作共享资源的途中发生了问题,导致锁失效,那这不止是 Redlock 的问题,任何其它锁服务例如 Zookeeper,都有类似的问题。
1. 客户端在拿到锁之前,无论经历什么耗时长问题,Redlock 都能够在第 3 步检测出来
2. 客户端在拿到锁之后,发生 NPC,那 Redlock、Zookeeper 都无能为力
Redlock 在保证时钟正确的基础上,是可以保证正确性的。

质疑 fencing token 机制
既然服务器都有了互斥能力,那还要分布式锁干什么?

ZooKeeper分布式锁优缺点

优点
1. 不需要考虑锁的过期时间
2. 加锁失败后,可以watch等待锁释放
缺点
1. 性能不如 Redis
2. 部署和运维成本高
3. 客户端与ZooKeeper的长时间失联,锁被释放问题

参考资料

使用Redis实现分布式锁和ZK实现分布式锁有什么区别,分别有哪些场景?

标签:加锁,Redlock,Redis,问题,实例,分布式,节点,客户端
From: https://www.cnblogs.com/WJQ2017/p/17557845.html

相关文章

  • 随笔-解决登录校验问题
    问题:在退出登录后(广义场景)在未重新登录的情况下仍能进入界面,用户数据保密问题没有解决。分析问题:需要进行登录校验(即当服务端接收到请求后,首先要将请求进行校验,如果已经登录,则正常访问,没有则返回错误结果)如何实现(会话技术,统一拦截技术):1.在登录成功后将登录成功的信息在一个地方......
  • 微服务或分布式场景,如何设计和使用分布式锁
    光谈论方式的话,太多了,数据库、jvm内存、redis、zookeeper都可以,最常用的是基于redis实现的redission框架 核心原理众多博客讲的很清楚,面试说个大概应该没问题了第一点,用的reids的setex命令,因为这个命令是原子操作,不会在设置锁的过程中出现意外第二点,锁过期问题,redission的解......
  • springboot中解决redissonClien无法注入,封装工具雷
    引用:https://blog.csdn.net/feiying0canglang/article/details/120464693问题来源前几天遇到一个循环依赖问题,是RedissonClient这个bean引起的。RedissonClient是由一个配置类(@Configuration注解的类)提供的,这配置类在初始化时(@PostConstruct注解的方法中)去获取RedissonClient这......
  • 实现concurrentHashMap与redis两级缓存
    一、实现concurrentHashMap与redis两级缓存以下是一种使用ConcurrentHashMap和Redis实现两级缓存的示例代码:importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cache.annotation.CacheEvict;importorg.springframework.cache.ann......
  • .NET面试题系列(23)tcp粘包问题
    序言 什么是粘包所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。解决粘包问题的方法 资料tcp粘包问题......
  • JAVA面试题----Redis
    Redisredis快的原因:完全基于内存操作,请求都在内存中所以快;它是单线程,省去了线程切换的时间和锁竞争的开销。采用io多路复用,多路指多个网络,对单个线程进行复用,避免了大量无用的操作。为什么使用redis:速度快,支持丰富的数据类型,redis集群不支持事务,内部单节点支持事务,操作都是原子......
  • Redis与Memcached有什么区别?
    Memcached与Redis有什么区别Redis和Memcached都是基于内存的数据存储系统,Memacched是高性能分布式内存缓存服务,其本质傻姑娘就是一个内存key-value数据库。Redis是一个开源的key-value存储系统,与Memcached类似,Redis将大部分数据存储在内存中,支持的数据类型,字符串,hash表,链表,集合,......
  • FeignClient服务端接口404问题
    自定义feignClient,服务端继承client接口,调用出现404异常。问题原因:feignClient接口的实现类上使用@Controller或@RestController的value属性中写入了公共url,导致接口方法404;因为feignClient是根据服务名在注册中心拉取到服务然后路由到feignClient接口的实现类上,跳过了类上的url......
  • 初识指针以及一些创建指针变量的常见问题和一些避免使用错误指针的方法
    在C语言中,指针是一种变量,用于存储另一个变量的内存地址。指针可以指向任何数据类型的变量,包括基本数据类型(如整型、字符型等)和复合数据类型(如数组、结构体等)。通过指针,我们可以直接访问和修改指向的变量的值,而不需要知道变量的名称。指针的声明使用星号(*)来表示,例如:int*ptr;//......
  • 时间回显问题
    使用  moment//安装npmimoment//vue引入方法 importmomentfrom"moment";  //直接赋值 leta=""//需要回显的时间 a= moment(请求过来的数据或需要赋的值); //传递使用函数变更时间格式或者组件自带的时间时间格式函数const filterTime=(time)=>......