Redis如何设置分布式锁

你好,我是吴计可师,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。

点击下方👇关注公众号,带你一起复习后端技术,看看面试考点,补充积累技术知识,每天都为面试准备积累

文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读


使用 Redis 设置 分布式锁 是一种常见的解决分布式系统中共享资源访问冲突的方法。Redis 的分布式锁可以有效地确保在多个客户端/服务中,只有一个客户端能持有锁并执行特定操作,防止数据不一致性。我们可以使用 Redis 的 SETNX 命令、EXPIRE 命令和 GETSET 命令来实现一个简单的分布式锁。

01
使用 SETNX 实现简单的分布式锁


Redis 提供的 SETNX(SET if Not eXists)命令可以保证只有当指定的键不存在时才会设置键值。在分布式锁的实现中,SETNX 命令用于设置锁键,如果锁键已经存在,其他客户端无法再次获取到锁。

  • SETNX lockKey value:如果 lockKey 不存在,Redis 设置这个键的值为 value,并返回 1;如果已经存在,返回 0。

为了避免锁的 死锁(锁被持有者异常退出未释放),我们通常为锁设置一个过期时间。这样,若锁持有者崩溃或未正常释放锁,其他进程仍能在过期后获取锁。

  • acquireLock(value):当获取锁时,使用 SETNX 设置锁并为其设置一个过期时间。只有当 LOCK_KEY 不存在时,才会成功设置。

  • releaseLock(value):释放锁时,首先检查锁的值是否是当前线程持有的值(防止释放其他客户端的锁),然后删除锁。


02
使用 SET 命令实现更加安全的分布式锁(推荐)


在 Redis 6.0 之后,推荐使用 SET 命令的 NX 和 EX 选项来实现分布式锁。SET 命令结合 NX(仅在键不存在时设置键)和 EX(设置键的过期时间)可以在一次命令中完成锁的获取与过期时间设置,比 SETNX 更加简洁和安全。

SET 命令语法:

SET key value NX EX seconds

  • NX:仅当 key 不存在时,才会设置。

  • EX seconds:设置键的过期时间(单位为秒)。

在这个实现中:

  • acquireLock(value):通过 SET 命令以原子方式设置锁,如果 LOCK_KEY 不存在,则设置锁并返回 "OK",并且设置了过期时间。

  • releaseLock(value):为了保证锁的释放是原子操作,使用 Lua 脚本。如果当前值与传入的值相同,则删除锁,否则不做任何操作。


03
处理死锁和锁超时


为了防止死锁,分布式锁需要考虑以下问题:

  • 锁超时:为了避免锁无法释放,应该给每个锁设置合理的过期时间(例如 30 秒)。如果操作未完成,过期时间到达后,锁会自动释放。

  • 锁竞争:在高并发的情况下,多个客户端可能同时尝试获取锁。可以设置 重试机制,例如,获取锁失败时重试若干次,或者等待一定时间后重试。

  • 防止误删除锁:释放锁时,必须确保只有持有锁的客户端才能删除锁。通过将锁的值设为一个唯一标识符(如 UUID),可以防止不同客户端误删其他客户端的锁。

04
Redlock:Redis 高可用分布式锁


为了提升 Redis 分布式锁的可靠性,特别是在 Redis 集群或主从复制的场景下,可以使用 Redlock 算法。Redlock 是 Redis 官方推荐的一种算法,它使用多个独立的 Redis 实例来实现高可用的分布式锁。Redlock 的核心思想是:

  • 使用多个 Redis 实例(至少 3 个)进行加锁。

  • 每个实例独立获取锁,确保某个 Redis 实例出现故障时,其他实例仍然能够提供锁的保证。

Redlock 的具体实现较为复杂,涉及到多个 Redis 节点之间的时钟同步和容错机制。对于常见的应用场景,建议使用 Redis 官方的简单分布式锁实现即可,只有在对锁的可用性要求极高时,才考虑使用 Redlock。



05
直击面试


5.1 为什么 Redis 分布式锁需要设置过期时间?

  • 设置过期时间的目的是防止因客户端崩溃或超时未释放锁而导致 死锁。

  • 如果没有设置过期时间,锁可能会长时间存在,导致其他客户端无法获取锁,从而影响系统的正常运行。

  • 通过 合理的过期时间(例如 30 秒),即使客户端崩溃或长时间未释放锁,其他客户端也可以重新尝试获取锁。


5.2 Redis 分布式锁如何避免死锁?

  • 死锁的主要问题是 锁未被释放,通常发生在持有锁的客户端崩溃或故障时。

  • 通过为分布式锁设置 过期时间 可以避免死锁。如果持有锁的客户端未能在一定时间内释放锁,锁会自动过期,其他客户端可以重新获取锁。

  • 锁的自动过期 是 Redis 分布式锁的一种常见策略,通过合理设置超时时间来减少死锁发生的概率。

  • 另外,可以通过 Redlock 算法 来增强分布式锁的可靠性。


5.3 Redis 分布式锁的常见问题有哪些?如何解决?

锁的误释放:如果没有确认是自己持有的锁就释放锁,可能会误释放其他客户端的锁。

  • 解决方法:可以在锁的值中存储一个唯一标识符(如 UUID),确保只有持锁的客户端才能释放锁。

锁的超时设置不当:如果锁的过期时间设置过短,可能会导致锁过早释放,造成业务竞争;如果设置过长,可能会导致死锁。

网络延迟与超时:网络延迟可能导致锁请求失败或超时。

  • 解决方法:根据业务需求合理设置锁的过期时间。可以采用 自旋 或 重试机制 来确保锁在合理时间内被成功获取。

  • 解决方法:可以使用 Redlock 等算法来提高锁请求的可靠性,减少单点故障带来的影响。


5.4 使用 Redis 实现分布式锁的优缺点是什么?

优点:

  • 高性能:Redis 本身是一个非常高效的内存数据库,能够提供低延迟的锁操作。

  • 简洁:使用 Redis 实现分布式锁的代码相对简单,容易集成。

  • 扩展性:Redis 支持分布式环境,可以通过 Redis Cluster 或 Sentinel 提供高可用性。

缺点:

  • 单点故障:如果只有一个 Redis 实例,可能会导致单点故障。可以通过 Redis Sentinel 或 Redis Cluster 来避免这个问题。

  • 锁失效风险:如果没有正确设置过期时间,可能会导致死锁或锁的延迟释放。

  • 性能瓶颈:当 Redis 负载过高时,可能会影响分布式锁的性能。


5.5 Redis 分布式锁如何支持高并发?

Redis 的 高性能 本身是支持高并发的,但为了在高并发环境下确保分布式锁的正确性和性能,可以:


  • 使用 合理的锁超时设置,确保在并发环境下能够及时释放锁。

  • 配置 Redis 集群 或 Redis Sentinel 来增加 Redis 的并发能力和可靠性。

  • 使用 Redlock 等算法在多个 Redis 实例中实现锁,避免单点故障和性能瓶颈。


5.6 Redis 分布式锁如何实现公平性?

Redis 默认的锁是 非公平的,即任何客户端在锁可用时都可以获得锁,而不考虑是否已经等待很久。

实现 公平性 可以通过引入 队列 来控制锁的请求顺序:

  • 每个请求锁的客户端都将其请求放入一个队列,按照请求的顺序进行处理。

  • 另一种方法是使用 Redisson,它支持公平锁(Fair Lock),通过内置的队列机制来确保客户端按照请求顺序依次获取锁。


今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!

END


扫码关注

一起积累后端知识
不积跬步,无以至千里
不积小流,无以成江海

喜欢此内容的人还喜欢

谈谈id那些事(五)——美团的 Leaf 的ID生成


一个阿里二面面试官必问的问题


谈谈id那些事(三)——阿里巴巴的 TDDL的ID生成


分享面试:mysql数据库索引失效的情况


面试常被忽略的问题——内存区域划分