分布式锁 redis与zookeeper

redis实现分布式锁

原理

基于redis命令setnx key value来实现分布式锁的功能,只有当key不存在时,setnx才可以设置成功并返回1,否则设置失败返回0。

方案1:

在这里插入图片描述

方案1存在的问题

假如在加锁成功,释放锁之前,服务器宕机了,这个锁就无法释放了,其他线程就永远无法获取到锁了。

改进之后

方案2

在这里插入图片描述

方案2存在的问题

获取锁和设置过期时间是2个命令,两次网络IO和redis交互,不具备原子性,可能出现获取锁成功之后 设置过期时间之前服务器宕机,导致其他线程永远都获取不到锁的现象。

方案3

1、将获取锁和设置过期时间放在lua脚本里面执行,lua脚本可以保证多个命令原子性的执行。
2、redis提供了其他命令在setnx的同时设置过期时间,分别如下所示:

  • set key value PX 多少毫秒 NX
  • set key value EX 多少秒 NX
    在这里插入图片描述
方案3存在的问题

锁过期释放了,但是业务仍没有执行完,等到执行完业务并释放锁的时候,释放的其实是其他线程获取到的锁。

  • 线程A获取到了锁,并设置过期时间10s。
  • 当过了10秒后,线程A仍然没有执行完,但是此时线程A获取到的锁已经达到了过期时间,导致锁被释放了。
  • 此时线程B获取到了锁,并执行业务方法
  • 线程A业务方法执行完,并执行释放锁逻辑(此时释放的是线程B获取到的锁)
  • 由于锁被释放,线程C也获取到了锁,并执行业务,此时线程B、线程C都获取到了同一个锁,并执行相应的业务。
方案4

在这里插入图片描述

方案4存在的问题

方案4中 判断key1的value1是否等于设置的值uuid,如果是则删除,否则不执行删除逻辑,由于是2个命令,不具备原子性导致可能出现以下场景

  • 线程A成功获取锁key1,设置key1的value为uuid1,并设置过期时间10秒。
  • 线程A执行完业务逻辑,准备删除锁
  • 线程A获取到key1的value uuid1。
  • 此时线程A的锁key1刚好到了过期时间
  • cpu时间片切换,线程A停止执行,线程B开始执行
  • 线程B尝试获取分布式锁key1(由于上面线程A设置的锁已经到了过期时间,所以此处可以获取成功)
  • cpu时间片切换,线程A继续执行,由于锁的value == uuid1,所以开始删除锁(此时删除的是线程B设置的分布式锁)。导致出现了删除其他线程设置的分布式锁。
方案五

在这里插入图片描述

方案5存在的问题

当前仍存在锁时间达到了过期时间,但是线程没执行完的问题。
如何解决呢?
watch dog机制(Redisson底层就是基于watch dog机制来实现分布式锁的自动续期,当未设置过期时间时,会默认设置30秒,并起一个看门狗线程,每十秒去检测一次,如果当前线程仍然在执行,就自动续期,当然,如果手动设置了过期时间,就不会自动续期了)。
在这里插入图片描述

方案五存在的问题

redis集群的场景下,redis-master节点设置成功了,但是redis-slave节点未设置成功,如下所示:

  • 当前redis集群有三个节点,分别是redis-master、redis-slave-a、redis-slave-b
  • 线程A尝试获取分布式锁,此时在redis-master上设置key1成功。
  • redis集群采用异步主从复制,但是redis-master上设置的key内容还没有同步到slave时,redis-master节点就宕机了。
  • 此时集群重新选举,因此redis-slave-a变成了mater节点
  • 此时有线程B尝试获取分布式锁key1,由于新的master节点上没有设置key1,因此线程B设置锁成功。
  • 当前线程A和线程B就都持有了分布式锁并执行业务逻辑。
方案6

如何解决方案五仍然存在的问题呢?
使用redlock(红锁)算法,这是一个专门用于解决这种问题的分布式锁协议。
redlock:不能只在一个redis实例上加锁,应该是在多个redis实例上创建锁,当创建成功数量达到(n/2 + 1)时,才能认为加锁成功

在这里插入图片描述
以上方式实现复杂、性能差、运维繁琐。
我们的redis是AP高可用思想、如果要实现强一致性,应该使用CP思想的zookeeper来实现,解决主从一致性问题。
C:Consistency;一致性
A:Availabilit;可用性
P:Partition Tolerance;分区容错性

zookeeper实现分布式锁

zookeeper的节点有以下几种类型;

  • 临时节点
    当客户端和zookeeper服务端断开连接时,临时节点就会被删除
  • 临时有序节点
    临时节点且都是有序的排列
  • 持久节点
    当客户端和zookeeper服务端断开连接时,持久节点也不会被删除
  • 持久有序节点
    持久节点且都有序的排列

zookeeper实现分布式锁就是利用到了临时有序节点。
在这里插入图片描述

区别

分布式系统中,**Redis** 和 **ZooKeeper** 均可用于实现分布式锁,但它们的底层机制、适用场景和优缺点不同。选择时需结合业务需求(如性能、一致性、容错性等)。以下是两者的详细对比和选型建议: --- ### **1. Redis 分布式锁** #### **实现方式** - **基于 SETNX(SET IF NOT EXISTS)**: 使用 `SET key value NX PX milliseconds` 命令(Redis 2.6.12+),通过唯一值(如 UUID)标识客户端,避免误删其他客户端的锁。 ```bash SET lock_key unique_value NX PX 30000 # 30秒后自动过期 ``` - **Redlock 算法**(Redis 官方推荐): 在多个独立的 Redis 节点上尝试加锁,需获得大多数节点的锁才算成功,提高可靠性。 #### **优点** - **高性能**:Redis 是内存数据库,操作耗时在微秒级,适合高并发场景。 - **简单易用**:API 直观,支持自动过期(避免死锁)。 - **灵活扩展**:可通过 Lua 脚本实现更复杂的逻辑(如锁续期)。 #### **缺点** - **非强一致性**:Redis 主从切换或集群分区时,可能导致锁丢失(如主节点未同步数据到从节点就崩溃)。 - **时钟漂移风险**:依赖服务器时间设置过期时间,若时钟不同步可能导致锁提前/延后释放。 - **需要处理锁续期**:若业务执行时间超过锁的 TTL,需手动续期(如 Redisson 的 `WatchDog` 机制)。 #### **适用场景** - 对性能要求高,允许偶尔锁失效(如秒杀系统、库存扣减)。 - 业务执行时间较短(远小于锁的 TTL)。 - 集群规模较小,网络分区概率低。 --- ### **2. ZooKeeper 分布式锁** #### **实现方式** - **临时顺序节点 + Watcher**: 1. 客户端在 `/locks` 目录下创建临时顺序节点(如 `/locks/lock-0000000001`)。 2. 检查自己是否是目录下最小的节点,若是则获取锁;否则监听前一个节点的删除事件。 3. 前一个节点释放时,Watcher 触发,当前节点重新检查是否为最小节点。 #### **优点** - **强一致性**:ZooKeeper 通过 ZAB 协议保证数据一致性,锁的获取和释放是原子性的。 - **天然防死锁**:临时节点在客户端断开连接时自动删除,避免锁残留。 - **顺序性支持**:可实现公平锁(按请求顺序获取锁)。 #### **缺点** - **性能较低**:ZooKeeper 是 CP 系统(优先保证一致性),每次操作涉及磁盘 I/O(写持久化日志),耗时在毫秒级。 - **实现复杂**:需处理节点监听、重试逻辑,代码量比 Redis 多。 - **集群规模受限**:ZooKeeper 适合节点数较少的集群(如 3-5 台),大规模部署成本高。 #### **适用场景** - 对一致性要求极高,不能容忍锁失效(如金融交易、分布式事务)。 - 业务执行时间不确定,需要可靠的锁释放机制。 - 集群规模较大,网络分区概率高。 --- ### **3. 关键对比** | **维度** | **Redis** | **ZooKeeper** | |------------------|------------------------------------|-----------------------------------| | **一致性** | 最终一致(AP 系统) | 强一致(CP 系统) | | **性能** | 高(微秒级) | 低(毫秒级) | | **实现复杂度** | 简单(单条命令或 Lua 脚本) | 复杂(需处理节点监听和顺序) | | **死锁风险** | 需手动续期或依赖 TTL | 临时节点自动释放,无死锁 | | **公平性** | 非公平锁(默认) | 可实现公平锁(按顺序获取) | | **适用场景** | 高并发、短任务 | 强一致、长任务 | --- ### **4. 选型建议** #### **选 Redis 的情况** - 业务允许偶尔锁失效(如日志处理、缓存更新)。 - 需要支持每秒数千次以上的锁请求。 - 团队熟悉 Redis 生态(如使用 Redisson 客户端)。 #### **选 ZooKeeper 的情况** - 业务必须保证锁的强一致性(如支付系统、分布式协调)。 - 任务执行时间较长(如分钟级),需避免锁被误删。 - 已有 ZooKeeper 集群,希望复用基础设施。 --- ### **5. 混合方案(可选)** - **Redis + ZooKeeper**: 用 Redis 实现高性能锁,ZooKeeper 作为后备(如 Redis 锁失效时通过 ZooKeeper 重试)。 - **ETCD**: 类似 ZooKeeper 的强一致系统,性能更优(基于 Raft 协议),适合 Kubernetes 生态。 --- ### **6. 最佳实践** - **Redis 锁**: - 使用 Redisson 等成熟客户端(内置锁续期、异常处理)。 - 锁的 TTL 设置为业务预期最大时间的 2-3 倍。 - **ZooKeeper 锁**: - 使用 Curator 框架简化实现(提供 `InterProcessMutex` 等工具类)。 - 避免在锁内执行耗时操作(如网络请求、磁盘 I/O)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值