你好,我是吴计可师,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。
文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读
使用 Redis 设置 分布式锁 是一种常见的解决分布式系统中共享资源访问冲突的方法。Redis 的分布式锁可以有效地确保在多个客户端/服务中,只有一个客户端能持有锁并执行特定操作,防止数据不一致性。我们可以使用 Redis 的 SETNX 命令、EXPIRE 命令和 GETSET 命令来实现一个简单的分布式锁。
SETNX lockKey value:如果 lockKey 不存在,Redis 设置这个键的值为 value,并返回 1;如果已经存在,返回 0。
为了避免锁的 死锁(锁被持有者异常退出未释放),我们通常为锁设置一个过期时间。这样,若锁持有者崩溃或未正常释放锁,其他进程仍能在过期后获取锁。
acquireLock(value):当获取锁时,使用 SETNX 设置锁并为其设置一个过期时间。只有当 LOCK_KEY 不存在时,才会成功设置。
releaseLock(value):释放锁时,首先检查锁的值是否是当前线程持有的值(防止释放其他客户端的锁),然后删除锁。
在 Redis 6.0 之后,推荐使用 SET 命令的 NX 和 EX 选项来实现分布式锁。SET 命令结合 NX(仅在键不存在时设置键)和 EX(设置键的过期时间)可以在一次命令中完成锁的获取与过期时间设置,比 SETNX 更加简洁和安全。
SET key value NX EX seconds
NX:仅当 key 不存在时,才会设置。
EX seconds:设置键的过期时间(单位为秒)。
在这个实现中:
acquireLock(value):通过 SET 命令以原子方式设置锁,如果 LOCK_KEY 不存在,则设置锁并返回 "OK",并且设置了过期时间。
releaseLock(value):为了保证锁的释放是原子操作,使用 Lua 脚本。如果当前值与传入的值相同,则删除锁,否则不做任何操作。
为了防止死锁,分布式锁需要考虑以下问题:
锁超时:为了避免锁无法释放,应该给每个锁设置合理的过期时间(例如 30 秒)。如果操作未完成,过期时间到达后,锁会自动释放。
锁竞争:在高并发的情况下,多个客户端可能同时尝试获取锁。可以设置 重试机制,例如,获取锁失败时重试若干次,或者等待一定时间后重试。
防止误删除锁:释放锁时,必须确保只有持有锁的客户端才能删除锁。通过将锁的值设为一个唯一标识符(如 UUID),可以防止不同客户端误删其他客户端的锁。
为了提升 Redis 分布式锁的可靠性,特别是在 Redis 集群或主从复制的场景下,可以使用 Redlock 算法。Redlock 是 Redis 官方推荐的一种算法,它使用多个独立的 Redis 实例来实现高可用的分布式锁。Redlock 的核心思想是:
使用多个 Redis 实例(至少 3 个)进行加锁。
每个实例独立获取锁,确保某个 Redis 实例出现故障时,其他实例仍然能够提供锁的保证。
Redlock 的具体实现较为复杂,涉及到多个 Redis 节点之间的时钟同步和容错机制。对于常见的应用场景,建议使用 Redis 官方的简单分布式锁实现即可,只有在对锁的可用性要求极高时,才考虑使用 Redlock。
设置过期时间的目的是防止因客户端崩溃或超时未释放锁而导致 死锁。
如果没有设置过期时间,锁可能会长时间存在,导致其他客户端无法获取锁,从而影响系统的正常运行。
通过 合理的过期时间(例如 30 秒),即使客户端崩溃或长时间未释放锁,其他客户端也可以重新尝试获取锁。
死锁的主要问题是 锁未被释放,通常发生在持有锁的客户端崩溃或故障时。
通过为分布式锁设置 过期时间 可以避免死锁。如果持有锁的客户端未能在一定时间内释放锁,锁会自动过期,其他客户端可以重新获取锁。
锁的自动过期 是 Redis 分布式锁的一种常见策略,通过合理设置超时时间来减少死锁发生的概率。
另外,可以通过 Redlock 算法 来增强分布式锁的可靠性。
锁的误释放:如果没有确认是自己持有的锁就释放锁,可能会误释放其他客户端的锁。
解决方法:可以在锁的值中存储一个唯一标识符(如 UUID),确保只有持锁的客户端才能释放锁。
锁的超时设置不当:如果锁的过期时间设置过短,可能会导致锁过早释放,造成业务竞争;如果设置过长,可能会导致死锁。
网络延迟与超时:网络延迟可能导致锁请求失败或超时。
解决方法:根据业务需求合理设置锁的过期时间。可以采用 自旋 或 重试机制 来确保锁在合理时间内被成功获取。
解决方法:可以使用 Redlock 等算法来提高锁请求的可靠性,减少单点故障带来的影响。
优点:
高性能:Redis 本身是一个非常高效的内存数据库,能够提供低延迟的锁操作。
简洁:使用 Redis 实现分布式锁的代码相对简单,容易集成。
扩展性:Redis 支持分布式环境,可以通过 Redis Cluster 或 Sentinel 提供高可用性。
缺点:
单点故障:如果只有一个 Redis 实例,可能会导致单点故障。可以通过 Redis Sentinel 或 Redis Cluster 来避免这个问题。
锁失效风险:如果没有正确设置过期时间,可能会导致死锁或锁的延迟释放。
性能瓶颈:当 Redis 负载过高时,可能会影响分布式锁的性能。
Redis 的 高性能 本身是支持高并发的,但为了在高并发环境下确保分布式锁的正确性和性能,可以:
使用 合理的锁超时设置,确保在并发环境下能够及时释放锁。
配置 Redis 集群 或 Redis Sentinel 来增加 Redis 的并发能力和可靠性。
使用 Redlock 等算法在多个 Redis 实例中实现锁,避免单点故障和性能瓶颈。
Redis 默认的锁是 非公平的,即任何客户端在锁可用时都可以获得锁,而不考虑是否已经等待很久。
实现 公平性 可以通过引入 队列 来控制锁的请求顺序:
每个请求锁的客户端都将其请求放入一个队列,按照请求的顺序进行处理。
另一种方法是使用 Redisson,它支持公平锁(Fair Lock),通过内置的队列机制来确保客户端按照请求顺序依次获取锁。
今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!