本文为【Redis分布式锁八股文合集(1)】初版,后续还会进行优化更新,欢迎大家关注交流~
hello hello~ ,这里是绝命Coding——老白~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
💥个人主页:绝命Coding-CSDN博客
💥 所属专栏:后端技术分享
这里将会不定期更新有关后端、前端的内容,希望大家多多点赞关注收藏💖
往期内容:
大厂面试官问我:Redis处理点赞,如果瞬时涌入大量用户点赞(千万级),应当如何进行处理?【后端八股文一:Redis点赞八股文合集】-CSDN博客
大厂面试官问我:布隆过滤器有不能扩容和删除的缺陷,有没有可以替代的数据结构呢?【后端八股文二:布隆过滤器八股文合集】-CSDN博客
大厂面试官问我:Redis持久化RDB有没有可能阻塞?阻塞点在哪里?【后端八股文三:Redis持久化八股文合集】-CSDN博客
大厂面试官问我:Redis内存淘汰,LRU维护整个队列吗?【后端八股文四:Redis内存淘汰策略八股文合集】-CSDN博客
下篇文章:
分布式锁
Java的锁只能保证单机的时候有效,分布式集群环境就无能为力了,这个时候我们就需要用到分布式锁
常见的解决方案
基于关系型数据库,如MySQL
基于关系型数据库实现分布式锁,是依赖数据库的唯一性来实现资源锁定,比如主键和唯一索引等。
基于Redis实现
Redis 锁实现简单,理解逻辑简单,性能好,可以支撑高并发的获取、释放锁操作。
缺点:
- Redis 容易单点故障,集群部署,并不是强一致性的,锁的不够健壮;
- key 的过期时间设置多少不明确,只能根据实际情况调整;
- 需要自己不断去尝试获取锁,比较消耗性能。
Redis实现分布式锁
加锁:
使用setnx来加锁。key是锁的唯一标识,按业务来决定命名,value这里设置为test。
setx key test
解锁
有加锁就得有解锁。当得到的锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式就是执行del指令。
del key
锁超时
锁超时知道的是:如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程北向进来。
所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一段时间后自动释放。setnx不支持超时参数,所以需要额外指令,
expire key 30
存在的问题
SETNX 和 EXPIRE 非原子性
由于setnx指令本身是不支持传入超时时间的,而在Redis2.6.12版本上为set指令增加了可选参数, 用法如下:
SET key value [EX seconds][PX milliseconds] [NX|XX]
锁误解除
如果线程 A 成功获取到了锁,并且设置了过期时间 30 秒,但线程 A 执行时间超过了 30 秒,锁过期自动释放,此时线程 B 获取到了锁;随后 A 执行完成,线程 A 使用 DEL 命令来释放锁,但此时线程 B 加的锁还没有执行完成,线程 A 实际释放的线程 B 加的锁。
解决办法:
在del释放锁之前加一个判断,验证当前的锁是不是自己加的锁。
具体在加锁的时候把当前线程的id当做value,可生成一个 UUID 标识当前线程,在删除之前验证key对应的value是不是自己线程的id。
还可以使用 lua 脚本做验证标识和解锁操作。
超时解锁导致并发
如果线程 A 成功获取锁并设置过期时间 30 秒,但线程 A 执行时间超过了 30 秒,锁过期自动释放,此时线程 B 获取到了锁,线程 A 和线程 B 并发执行。
A、B 两个线程发生并发显然是不被允许的,一般有两种方式解决该问题:
- 将过期时间设置足够长,确保代码逻辑在锁释放之前能够执行完成。
- 为获取锁的线程增加守护线程,为将要过期但未释放的锁增加有效时间。
4、不可重入
当线程在持有锁的情况下再次请求加锁,如果一个锁支持一个线程多次加锁,那么这个锁就是可重入的。如果一个不可重入锁被再次加锁,由于该锁已经被持有,再次加锁会失败。Redis 可通过对锁进行重入计数,加锁时加 1,解锁时减 1,当计数归 0 时释放锁。
Redis 为什么能做分布式锁
- 原子性操作: Redis 提供了一些原子性操作,如 SET key value NX 命令,可以确保在同一时刻只有一个客户端成功获取锁。这种原子性操作可以确保分布式环境下的竞争条件。
- 持久性: Redis 是基于内存的数据库,数据可以持久化到磁盘,即使系统重启,之前获取的锁也不会丢失。这保证了分布式锁的持久性。
- 可靠性: Redis 是一个高可用的分布式系统,通过复制和故障转移机制,可以确保即使部分节点宕机,锁也不会丢失。
- 性能: Redis 的读写性能非常高,可以满足分布式环境下对锁的性能需求。
Redis分布式锁会有什么问题?是cp还是ap?有没有其它解决方案?/ Redis分布式锁缺陷? / Redis分布式锁有什么问题 为什么需要用lua脚本?
- 单点故障: 如果 Redis 服务器宕机,将会导致整个分布式系统无法获取锁,存在单点故障风险。
- 锁超时问题: 客户端在获取锁后可能由于各种原因(网络问题、程序异常等)而无法及时释放锁,这会导致其他客户端无法获取锁,出现死锁问题。
- 安全性问题: 如果客户端在获取锁后崩溃,而没有正确释放锁,其他客户端将无法获取锁。为了解决这个问题,通常需要使用 Lua 脚本来实现锁的释放,以确保原子性。
Redis 分布式锁属于 CP 模型,即满足一致性(Consistency)和分区容忍性(Partition Tolerance),但不能完全满足可用性(Availability)。
原因如下:
- 一致性(Consistency): Redis 分布式锁通过原子性操作(如 SET key value NX)来保证同一时刻只有一个客户端获取到锁,满足一致性要求。
- 分区容忍性(Partition Tolerance): Redis 作为一个分布式系统,能够容忍网络分区的发生,仍然能够提供服务。
- 可用性(Availability): 由于 Redis 分布式锁存在单点故障的风险,当 Redis 服务器宕机或网络分区导致无法连接 Redis 时,客户端就无法获取锁,违背了可用性原则。
因此,Redis 分布式锁属于 CP 模型,即满足一致性和分区容忍性,但不能完全满足可用性。为了提高可用性,可以考虑使用 Redlock 算法或 Zookeeper 等其他分布式锁方案
为了解决这些问题,可以考虑以下方案:
- Redlock 算法: Redlock 是一种基于多个 Redis 实例的分布式锁算法,可以提高可靠性和可用性。
- Zookeeper 分布式锁: Zookeeper 提供了一种基于 Znode 的分布式锁机制,可以解决 Redis 单点故障的问题。
- Consul 分布式锁: Consul 也提供了一种基于 Key-Value 存储的分布式锁机制,可以作为 Redis 分布式锁的替代方案。
- 基于数据库的分布式锁: 可以使用数据库的行级锁或表级锁来实现分布式锁,这种方式可靠性较高,但性能可能不如 Redis 或 Zookeeper。
为什么需要使用Lua脚本:
-
- Lua 脚本可以确保释放锁的操作是原子性的,避免了竞争条件。
- 使用 Lua 脚本可以检查当前锁的所有者是否与当前客户端一致,从而避免误删除其他客户端的锁。
为什么能满足分布式环境下的需求?
- 原子性: Redis 的 SET key value NX 命令是原子性的,可以确保同一时刻只有一个客户端获取到锁。
- 持久性: Redis 支持数据持久化,即使系统重启,之前获取的锁也不会丢失。
- 高可用性: Redis 集群支持主从复制和故障转移,能够提高可用性,减少单点故障。
Redis的分布式锁,锁失效了该怎么办?
- 如果锁超时或客户端崩溃导致锁失效,可以设置一个自动续期机制,定期检查并延长锁的过期时间。
- 如果锁已经失效,可以让客户端重新尝试获取锁。
Redis的分布式锁是公平锁还是非公平锁?
- Redis 分布式锁是非公平锁,因为获取锁的顺序取决于客户端的执行速度,而不是按照请求顺序。
Redis分布式锁主从架构锁失效问题如何解决?
- 可以使用 Redlock 算法,它在多个 Redis 实例上获取锁,只有在大多数实例上获取成功才算获取锁成功。这样可以提高可用性和容错性。
Redlock 算法是什么?
Redlock 算法是一种用于实现分布式锁的算法,它建立在 Redis 分布式锁的基础之上,旨在提高分布式锁的可用性和容错性。
Redlock 的主要实现步骤如下:
- 获取当前时间戳
- 尝试在 N 个独立的 Redis 实例上获取锁
-
- 在每个 Redis 实例上执行 SET key value NX PX milliseconds 命令获取锁
- 如果在大多数 Redis 实例(N/2 + 1)上成功获取锁,则认为获取锁成功
- 计算获取锁总共耗时
- 如果获取锁成功,且总耗时小于锁的有效时间的 1/2,则认为获取锁成功
- 如果获取锁失败,则在所有成功获取锁的实例上执行 DEL key 命令来释放锁
Redlock 算法的优点如下:
- 提高可用性: 即使某个 Redis 实例不可用,只要大多数实例可用,客户端仍能获取到锁,提高了可用性。
- 提高容错性: 即使某个 Redis 实例出现问题,客户端也能在其他实例上获取到锁,减少了单点故障的风险。
- 强一致性: Redlock 算法采用"大多数获胜"的方式,可以确保同一时刻只有一个客户端获取到锁,满足强一致性要求。
缺点是:
- 需要多个独立的 Redis 实例,增加了系统复杂度和部署成本。
- 如果大部分 Redis 实例不可用,客户端可能无法获取到锁,降低了可用性。
用原生redis实现分布式锁,锁误删的情况?
- 使用 Lua 脚本可以确保释放锁的操作是原子性的,避免了竞争条件和误删除其他客户端的锁。
Redis分布式锁底层实现?
- Redis分布式锁的底层主要依赖于Redis的SET key value NX命令。
- 该命令可以确保在同一时刻只有一个客户端成功获取锁。如果key已经存在,则返回NULL,否则将key-value对设置成功并返回OK。
- 释放锁则是通过DEL key命令来删除对应的key。
单个节点和多个节点的redis分布式锁有什么区别?
- 单个节点的Redis分布式锁:
-
- 只需要在单个Redis实例上获取和释放锁。
- 如果Redis实例宕机或网络中断,则会导致锁失效,出现单点故障问题。
- 多个节点的Redis分布式锁(Redlock算法):
-
- 需要在多个独立的Redis实例上获取锁。
- 只有在大多数Redis实例(N/2 + 1)上成功获取锁,才算获取锁成功。
- 即使有个别Redis实例不可用,只要大多数实例可用,客户端仍能获取到锁,提高了可用性和容错性。
除了redis还有哪些可以实现分布式锁? / 可以做分布式锁的除了redis还有哪些?/ 除了Redis,ZooKeeper还知道哪些分布式锁
- Zookeeper: Zookeeper提供了持久化的顺序节点,可以实现分布式锁。Zookeeper分布式锁具有高可用性和强一致性。
- Mysql:优点是实现简单,容易理解。缺点是性能一般,并且需要额外维护一张锁表。
redis分布式锁,为什么value设置成uuid,避免误删?细说场景
- 设置value为UUID是为了避免误删其他客户端的锁。
- 如果不设置UUID,当一个客户端获取锁后宕机或网络中断,无法释放锁。此时其他客户端尝试删除锁时,可能会误删除其他客户端的锁。
- 设置value为UUID后,客户端在释放锁时可以先检查value是否等于自己的UUID,从而确保只释放自己持有的锁,避免了误删除的问题。
分布式锁怎么选型,zk和redis 比较下 / 为什么用redis分布式锁,而不是考虑用别的锁 / Redis 实现分布式锁 和 ZooKeeper 实现分布式锁优缺点对比?/ zookeeper结构 与redis分布式锁区别 / Redis 和 zookeeper 那个好,如果是集群部署,高并发情况下哪个性能更好
- 性能:
-
- Redis 分布式锁的性能较高,Redis 本身是一个高性能的内存数据库,获取和释放锁的操作都很快。作为一个内存数据库,在单机和集群场景下都具有非常出色的性能和吞吐量。在高并发场景下,Redis 分布式锁的性能会更加优秀。
- ZooKeeper 分布式锁相对来说性能略差一些,因为 ZooKeeper 是一个专门的分布式协调服务,涉及多次 RPC 调用。作为一个分布式协调系统,性能相对会弱一些。但通过合理的集群配置,ZooKeeper 也可以支撑较高的并发场景。
- 可靠性和容错性:
-
- ZooKeeper 分布式锁具有更高的可靠性和容错性,因为 ZooKeeper 是一个高可用的分布式系统,即使个别节点宕机,也不会影响整体的可用性。
- Redis 分布式锁如果使用单节点,则存在单点故障的风险。不过可以采用 Redis Cluster 或者 Redis Sentinel 等方式来提高可用性。
- 实现复杂度:
-
- Redis 分布式锁的实现比较简单,只需要利用 Redis 的 SET key value NX 命令即可。
- ZooKeeper 分布式锁的实现相对复杂一些,需要利用 ZooKeeper 的顺序节点和监听机制来实现。
- 一致性:
-
- ZooKeeper 分布式锁能提供更强的一致性保证,因为 ZooKeeper 是基于 Paxos 算法实现的。
- Redis 分布式锁虽然也可以通过 Redlock 算法来提高一致性,但实现相对复杂。
- 使用场景:
-
- 对于追求高性能和简单实现的场景,Redis 分布式锁是一个不错的选择。
- 对于追求高可用性和强一致性的场景,ZooKeeper 分布式锁可能更合适。
为什么zk实现的分布式锁可以做到强一致,redis分布式不能
- 一致性模型:
-
- ZooKeeper 使用 Paxos 算法实现了强一致性的复制状态机。这意味着 ZooKeeper 集群中的所有节点都能保证数据的一致性。
- Redis 虽然也有集群模式,但它采用的是最终一致性模型,即节点之间的数据可能会存在短暂的不一致。
- 锁机制:
-
- ZooKeeper 分布式锁是基于顺序节点实现的。客户端在获取锁时会在一个特定的节点下创建一个有序的子节点,子节点的序号决定了获得锁的顺序。这种机制能够保证锁的公平性和一致性。
- Redis 分布式锁一般是通过 SET key value NX 这种原子操作实现的,这种方式无法保证完全的公平性和一致性。
- 故障处理:
-
- ZooKeeper 集群采用主备复制,即使个别节点宕机,集群仍能保持运行。同时 ZooKeeper 还提供了节点监听机制,可以及时感知并处理节点故障。
- Redis 如果使用单节点,存在单点故障的风险。即使使用了 Redis Cluster,在节点故障时也可能会出现短暂的不一致。
是否了解Redisson分布式锁的实现原理
- Redisson 采用了一种称为"红锁"的算法,结合了 Redis 的 SET key value NX 命令和 Redlock 算法。
- 具体来说,Redisson 会在多个 Redis 节点上同时获取锁,只有当大多数节点(例如 3 个节点中的 2 个)都成功获取锁,才认为整个分布式锁获取成功。这种方式可以提高分布式锁的可靠性。
使用单个键作为分布式锁的热键问题,如何解决
- 使用单个键作为分布式锁会导致该键成为热点,承受很大的访问压力。
- Redisson 的解决方案是:根据锁的名称计算出多个 Redis 键,在这些键上进行加锁和释放锁操作,从而分散热点压力。
为什么需要使用Redisson加看门狗实现分布式锁
- 在使用分布式锁时,客户端可能会由于各种原因(例如网络中断、程序崩溃等)无法及时释放锁,导致锁一直被占用。
- Redisson 的看门狗机制可以解决这个问题。它会定期检查锁的状态,如果发现锁已经过期,则会自动释放该锁,防止出现死锁的情况。
Redission的watchdog原理
- Redisson 的看门狗是一个独立的线程,它会定期去检查所有获得锁的客户端,判断锁是否已经过期。
- 如果发现锁已经过期,看门狗会自动释放该锁,防止死锁的发生。
后期新的八股文合集文章会继续分享,感兴趣的小伙伴可以点个关注~
更多精彩内容以及免费资料请关注公众号:绝命Coding