分布式锁实现涉及Redis、ZK与etcd三种技术栈,在锁语义与故障恢复上各有差异。对比Redis Redlock的多数派投票、ZK临时节点Watch与etcd Lease续约三种方案,分析TTL设置与锁安全性在工程中的取舍。内容兼顾理论推导、工程实践与生产案例复盘,帮助架构师在高并发与高可用场景下的设计决策与故障排查中做出正确判断。
需要学习分布式锁实现(Redis/ZK/etcd),分布式锁实现涉及Redis、ZK与etcd三种技术栈,在锁语义与故障恢复上各有差异。分布式锁实现(Redis/ZK/etcd)属于分布式存储与协调 > 分布式协调服务的关键内容,掌握后可更好地衔接后续章节。
目录
可视化演示
对比Redis SETNX、ZK临时节点Watch与Redlock多数派三种分布式锁方案的获取、续约与释放机制,理解TTL与锁安全性的取舍,可切换三种模式。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Redis锁:SETNX SETNX保证key不存在时才能设置成功 TTL超时自动释放,防持锁崩溃死锁 SET lock NX EX ttl 性能高实现简单,但主从复制可能丢锁。
分布式锁的基本概念与核心定义
分布式锁是分布式系统中用于协调多个节点对共享资源互斥访问的核心机制。在单机环境中,我们可以通过进程内的锁(如互斥锁、信号量)来实现对共享资源的并发控制;但在分布式环境中,多个进程可能运行在不同的物理节点上,传统的进程内锁无法跨越网络边界提供互斥保证。分布式锁通过引入一个独立的协调服务(如Redis、ZooKeeper、etcd),使得分布在不同节点上的进程能够在该协调服务的帮助下实现跨网络的互斥访问控制。
分布式锁需要满足三个核心属性:安全性(Safety)——任何时候最多只有一个节点持有锁,确保互斥性;活性(Liveness)——每个请求获取锁的节点最终都能获得锁,不会无限等待;效率(Efficiency)——锁的获取和释放操作应当具有可接受的时间复杂度和网络开销。这三个属性对应了分布式系统中经典的安全性属性和活性属性,而FLP不可能定理告诉我们,在异步网络中即使只有一个节点可能发生故障,也不存在一个确定性的算法能够同时保证安全性和活性,因此实际系统中的分布式锁实现往往需要在这些属性之间做出权衡。
与本地锁相比,分布式锁具有显著的差异:本地锁依赖于共享内存和原子指令(如CAS),延迟极低(纳秒级);而分布式锁需要通过网络通信来协调,延迟通常在毫秒级别,且面临网络分区、节点故障、时钟漂移等分布式系统特有的挑战。常见的分布式锁实现方案包括基于Redis的SETNX命令、基于ZooKeeper的临时顺序节点、以及基于etcd的Lease和事务机制,每种方案在一致性保证、性能、可用性等方面各有侧重。
分布式锁的工作原理与核心机制
基于Redis实现分布式锁的核心命令是SETNX(SET if Not eXists),客户端通过执行SET lock_key unique_value NX EX timeout来尝试获取锁,其中NX保证只有在key不存在时才能设置成功,EX设置锁的自动过期时间以防止死锁。释放锁时,通过Lua脚本原子性地检查锁的值是否匹配当前客户端标识,匹配则删除该key,这种设计避免了误删其他客户端持有的锁。Redlock算法是Redis作者Antirez提出的多节点分布式锁方案,客户端向N个独立的Redis实例依次尝试获取锁,当且仅当在超过半数实例上成功获取锁且总耗时未超过锁的有效时间时,才认为获取锁成功。
基于ZooKeeper实现分布式锁利用的是其临时顺序节点(Ephemeral Sequential Node)机制。客户端在锁对应的ZNode路径下创建一个临时顺序节点,ZooKeeper保证每个临时顺序节点获得全局唯一的递增序号;客户端获取所有子节点列表并排序,如果自己创建的节点序号最小则获取锁成功,否则对前一个节点注册Watcher监听,当前一个节点被删除时(即前一个锁持有者释放锁或崩溃导致临时节点自动删除),客户端收到通知后再次检查自己是否为最小序号节点。这种机制天然实现了公平锁和排队等待,且临时节点的生命周期与客户端会话绑定,客户端崩溃后会话超时后节点自动删除,避免了死锁。
基于etcd实现分布式锁主要利用Lease(租约)和Txn(事务)机制。客户端首先创建一个Lease并设置TTL(Time To Live),然后通过Txn原子操作尝试将锁对应的key与Lease关联——如果key不存在则创建成功(获取锁),否则获取失败。etcd的Lease机制确保即使客户端崩溃,Lease到期后关联的key也会自动删除,从而释放锁。锁的续期通过定时发送Lease KeepAlive请求实现,类似于Redis中的watchdog机制。etcd基于Raft共识算法,保证了锁操作的强一致性,任何时刻全局只有一个客户端能成功创建锁key。
分布式锁的主要类型与分类体系
分布式锁按照不同的维度可以进行多种分类。按锁的获取策略可分为悲观锁和乐观锁:悲观锁假设冲突必然发生,在操作数据前先获取排他锁,适用于写多读少的场景;乐观锁假设冲突较少发生,在更新数据时通过版本号或时间戳进行冲突检测,冲突时重试,适用于读多写少的场景。按锁的互斥程度可分为独占锁(排他锁)和共享锁:独占锁保证同一时刻只有一个客户端持有锁进行写操作;共享锁允许多个客户端同时持有锁进行读操作,但写操作需要获取独占锁,这种读写锁模式在缓存更新等读多写少场景中能显著提升并发性能。
按锁的可重入性可分为可重入锁和不可重入锁:可重入锁允许同一个客户端多次获取同一把锁而不会死锁,内部通过记录锁持有者标识和重入计数来实现;不可重入锁则不允许同一客户端重复获取,重复获取会导致死锁或失败。按锁的公平性可分为公平锁和非公平锁:公平锁按照请求顺序依次分配锁,每个客户端最终都能获得锁,但吞吐量较低;非公平锁允许插队获取锁,吞吐量高但可能导致某些客户端长时间等待。ZooKeeper基于顺序节点天然实现公平锁,Redis的Redlock本质上是非公平锁。
从底层一致性模型的角度对比三大分布式锁方案:Redis基于AP模型,采用主从异步复制,在网络分区时仍可提供锁服务但可能丢失锁信息导致互斥性被破坏,Redlock尝试通过多节点投票缓解这一问题但仍有争议;ZooKeeper基于CP模型,使用ZAB协议保证强一致性,在网络分区时可能拒绝服务但不会破坏互斥性,其临时节点和Watch机制为锁实现提供了优雅的故障检测能力;etcd同样基于CP模型,使用Raft共识算法,通过Lease机制实现锁的自动释放,其强一致性保证使得锁的安全性最为可靠,但写操作需要多数派确认导致延迟相对较高。
分布式锁的实际应用场景与案例分析
订单去重是分布式锁最经典的应用场景之一。在电商系统中,用户可能因为网络延迟或误操作导致同一订单被重复提交,此时可以使用Redis分布式锁来保证幂等性:以订单号作为锁的key,在创建订单前先尝试获取锁,获取成功则执行业务逻辑创建订单,获取失败则说明订单正在处理中或已创建完成,直接返回。类似地,在分布式限流场景中,可以使用分布式锁实现全局计数器,多个服务节点共享同一个计数器来精确控制总请求速率,避免单节点限流无法控制全局流量的问题。
Leader选举是分布式系统中另一个重要应用场景,ZooKeeper是实现Leader选举的经典选择。在分布式模型推理服务中,需要选举一个Leader节点负责模型版本管理、负载均衡策略更新和故障节点检测等协调工作。所有候选节点在ZooKeeper的指定路径下创建临时节点,序号最小的节点成为Leader;当Leader节点崩溃时,其临时节点被自动删除,其余节点中序号最小的自动成为新的Leader,整个过程无需人工干预。etcd同样可以通过选举模块(Election)实现Leader选举,其基于Raft的强一致性保证了选举结果的正确性。
分布式调度器协调是分布式锁在企业级应用中的重要场景。在大规模分布式任务调度系统中,多个调度器实例可能同时检测到某个定时任务需要执行,此时通过分布式锁确保同一时刻只有一个调度器实例执行该任务,避免任务重复执行导致的数据不一致问题。在配置管理场景中,etcd的分布式锁被用于保证配置变更的原子性——当多个管理员同时修改配置时,通过获取分布式锁确保配置变更串行化执行,配合etcd的Watch机制将配置变更实时通知到所有订阅节点。
分布式锁的优缺点分析与选型建议
Redis分布式锁的优点是性能极高(单机QPS可达10万+)、实现简单、支持丰富的数据结构便于扩展;缺点是Redis采用主从异步复制,在主节点故障切换到从节点时可能丢失锁信息,导致多个客户端同时持有锁,Redlock算法虽然通过多独立节点投票来缓解这一问题,但Martin Kleppmann指出Redlock在某些时序场景下仍然无法保证安全性,且引入多个独立Redis节点增加了运维复杂度。Redis分布式锁适合对性能要求极高、允许极小概率锁失效的业务场景,如缓存防击穿、简单的幂等控制等。
ZooKeeper分布式锁的优点是强一致性保证(基于ZAB协议)、临时节点机制天然支持故障检测、顺序节点天然支持公平锁、Watch机制支持事件驱动;缺点是ZooKeeper的延迟相对较高(因为需要多数派确认写入),且ZooKeeper集群的运维较为复杂(需要维护ZNode树、处理会话超时等),在高并发锁竞争场景下性能不如Redis。ZooKeeper分布式锁适合对一致性要求严格、需要公平锁语义、对延迟不太敏感的核心业务场景,如金融交易的分布式协调、数据库DDL变更锁等。
etcd分布式锁的优点是基于Raft共识算法提供强一致性保证、Lease机制提供了灵活的锁超时和自动释放能力、API设计简洁且原生支持分布式锁原语;缺点是etcd的写性能受限于Raft多数派确认的延迟,运维复杂度较高(需要维护Raft集群、处理Leader选举和网络分区),且在跨地域部署场景下延迟会进一步增大。选型建议:追求极致性能且可接受极小概率不一致选Redis;追求强一致性和可靠性选etcd或ZooKeeper;已有ZooKeeper基础设施的团队优先使用ZooKeeper;云原生环境推荐使用etcd(Kubernetes原生组件)。
分布式锁的进阶话题与发展趋势
Redlock争议是分布式系统领域最著名 technical debate 之一。2016年Martin Kleppmann发表文章"How to do distributed locking"质疑Redlock的安全性,核心论点是:Redlock依赖各节点的时钟独立性来保证锁的互斥性,但在现实中时钟跳跃(如NTP同步、闰秒调整)是常见的,当一个Redis节点发生时钟前跳时,可能导致锁提前过期而被另一个客户端获取,此时前一个客户端仍在执行临界区代码,互斥性被破坏。Antirez随后进行了反驳,认为Kleppmann假设的时序场景在实际中概率极低且可以通过严格的时钟管理来规避。这场争论的核心启示是:基于时钟超时的分布式锁在理论上无法提供绝对的安全性保证。
无锁算法(Lock-Free Algorithms)和乐观并发控制(Optimistic Concurrency Control, OCC)是分布式锁的替代方案。无锁算法利用CAS(Compare-And-Swap)等原子操作实现线程间的协调,避免了锁的开销和死锁风险,但在分布式环境中实现高效的无锁算法仍然具有挑战性。OCC假设冲突较少,在操作开始时不获取锁,而是在提交时进行冲突检测——通过版本号或时间戳判断数据是否被其他客户端修改过,如果检测到冲突则回滚重试。Google的Percolator模型是OCC在分布式事务中的经典实现,其通过为每次事务分配全局唯一的时间戳来保证事务的可串行化。
分布式事务作为分布式锁的替代方案在某些场景下更为合适。两阶段提交(2PC)和三阶段提交(3PC)协议通过协调者统一管控所有参与者的提交或回滚决策,保证了跨节点操作的原子性,但其同步阻塞特性导致性能较低。近年来,云原生锁服务(如AWS DynamoDB Lock Client、Google Chubby)提供了托管的分布式锁能力,降低了用户的运维负担;同时,基于向量时钟和因果一致性的高效并发控制方案也在不断发展,有望在未来提供更优的分布式并发控制解决方案。
分布式锁的实践指南与最佳实践
锁超时时间的设置是分布式锁实践中最关键的参数之一。超时时间过短会导致业务逻辑尚未执行完毕锁就被自动释放,其他客户端获取到锁后并发执行造成数据不一致;超时时间过长会导致锁持有者崩溃后其他客户端需要等待很长时间才能获取锁,降低系统可用性。最佳实践是采用“合理初始超时 + Watchdog自动续期”的组合策略:初始设置一个合理的超时时间(通常为业务执行预期时间的2-3倍),同时启动一个后台Watchdog线程定期(如每10秒)检查锁是否仍被当前客户端持有且业务是否仍在执行,如果是则延长锁的超时时间。Redisson框架的WatchDog机制和etcd的Lease KeepAlive都是这一策略的典型实现。
避免死锁和正确处理锁持有者崩溃是分布式锁实践中的核心挑战。死锁可能由以下原因导致:客户端获取锁后发生异常但未释放锁且锁未设置超时时间、多个锁的获取顺序不一致导致循环等待、Watchdog续期失败但客户端误认为仍持有锁。最佳实践包括:始终为锁设置合理的超时时间作为兜底机制、使用唯一的客户端标识(如UUID)作为锁的值防止误删他人锁、释放锁时通过原子操作(Lua脚本或CAS)验证锁的归属、实现优雅的错误处理确保在异常路径上也能释放锁。当检测到锁持有者崩溃时,应依赖超时机制或会话机制(如ZooKeeper临时节点)自动释放锁。
监控锁竞争和测试分布式锁实现是保障系统稳定运行的重要环节。在生产环境中,应监控以下指标:锁获取的成功率和平均延迟、锁的持有时间分布、锁竞争频率(获取失败的比率)、锁超时事件的频率、Watchdog续期的成功率。当锁竞争过于激烈时,可以考虑优化锁粒度(将粗粒度锁拆分为细粒度锁)、引入锁等待队列、或者改用乐观并发控制策略。在测试方面,应使用混沌工程(Chaos Engineering)方法模拟各种故障场景:网络延迟、节点崩溃、网络分区、时钟跳跃等,验证分布式锁实现在异常情况下的正确性和鲁棒性。
AI 应用场景
GPU资源分配中的分布式锁
解决的问题:在机器学习训练集群中,多个训练任务可能竞争有限的GPU资源。通过Redis分布式锁实现GPU资源的互斥分配:每个GPU设备对应一把分布式锁,训练任务在启动前先尝试获取目标GPU的锁,获取成功后独占该GPU进行训练,训练完成后释放锁。结合锁超时机制,即使训练任务异常退出,GPU资源也能在超时后自动释放,避免GPU资源被永久占用。
具体案例:在机器学习训练集群中,多个训练任务可能竞争有限的GPU资源。通过Redis分布式锁实现GPU资源的互斥分配:每个GPU设备对应一把分布式锁,训练任务在启动前先尝试获取目标GPU的锁,获取成功后独占该GPU进行训练,训练完成后释放锁。结合锁超时机制,即使训练任务异常退出,GPU资源也能在超时后自动释放,避免GPU资源被永久占用。
分布式模型推理协调器的Leader选举
解决的问题:在大规模模型推理服务中,多个推理节点需要协调工作:一个Leader节点负责模型版本切换、负载均衡策略更新和健康检查,其余节点执行实际的推理请求处理。使用ZooKeeper临时顺序节点实现Leader选举,Leader节点崩溃后自动触发重新选举,新的Leader接管协调职责。ZooKeeper的Watch机制可以实时通知所有节点Leader变更事件,确保推理集群快速响应拓扑变化。
具体案例:在大规模模型推理服务中,多个推理节点需要协调工作:一个Leader节点负责模型版本切换、负载均衡策略更新和健康检查,其余节点执行实际的推理请求处理。使用ZooKeeper临时顺序节点实现Leader选举,Leader节点崩溃后自动触发重新选举,新的Leader接管协调职责。ZooKeeper的Watch机制可以实时通知所有节点Leader变更事件,确保推理集群快速响应拓扑变化。
防止重复模型训练任务的分布式锁
解决的问题:在MLOps平台中,用户可能重复提交相同的模型训练任务,或者调度系统因故障重试导致同一训练任务被多次触发。使用etcd分布式锁实现训练任务的幂等性保证:以训练任务的唯一标识(如任务ID+参数哈希)作为锁的key,在启动训练前先获取锁,获取成功则执行训练,获取失败则说明任务已在执行中。etcd的强一致性保证了即使在高并发场景下也不会出现重复训练。
具体案例:在MLOps平台中,用户可能重复提交相同的模型训练任务,或者调度系统因故障重试导致同一训练任务被多次触发。使用etcd分布式锁实现训练任务的幂等性保证:以训练任务的唯一标识(如任务ID+参数哈希)作为锁的key,在启动训练前先获取锁,获取成功则执行训练,获取失败则说明任务已在执行中。etcd的强一致性保证了即使在高并发场景下也不会出现重复训练。
Python 演示
import timeimport threadingimport hashlibimport uuid class RedisLockSimulator: """模拟Redis SETNX分布式锁""" def __init__(self): self._store = {} self._lock = threading.Lock() def setnx(self, key, value, ex=None): with self._lock: if key in self._store: return False self._store[key] = { 'value': value, 'expire': time.time() + ex if ex else None } return True def delete(self, key, value): with self._lock: if key in self._store and self._store[key]['value'] == value: del self._store[key] return True return False def is_expired(self, key): with self._lock: if key not in self._store: return True entry = self._store[key] if entry['expire'] and time.time() > entry['expire']: del self._store[key] return True return False def extend_expire(self, key, value, additional_time): with self._lock: if key in self._store and self._store[key]['value'] == value: self._store[key]['expire'] = time.time() + additional_time return True return False class DistributedLock: """基于Redis模拟的分布式锁实现""" def __init__(self, redis_sim, name, client_id=None, timeout=30): self._redis = redis_sim self._name = name self._client_id = client_id or str(uuid.uuid4()) self._timeout = timeout self._is_locked = False self._watchdog_thread = None self._watchdog_running = False def acquire(self, blocking=True, retry_interval=0.3): while True: acquired = self._redis.setnx( self._name, self._client_id, ex=self._timeout ) if acquired: self._is_locked = True self._start_watchdog() return True if not blocking: return False time.sleep(retry_interval) def release(self): if not self._is_locked: return False self._stop_watchdog() result = self._redis.delete(self._name, self._client_id) self._is_locked = False return result def _start_watchdog(self): self._watchdog_running = True self._watchdog_thread = threading.Thread( target=self._watchdog_loop, daemon=True ) self._watchdog_thread.start() def _stop_watchdog(self): self._watchdog_running = False if self._watchdog_thread: self._watchdog_thread.join(timeout=2) def _watchdog_loop(self): interval = self._timeout / 3 while self._watchdog_running: time.sleep(interval) if self._watchdog_running: self._redis.extend_expire( self._name, self._client_id, self._timeout ) class ZKLockSimulator: """模拟ZooKeeper临时顺序节点分布式锁""" def __init__(self): self._nodes = {} self._counter = 0 self._lock = threading.Lock() self._sessions = {} def create_session(self, session_id): self._sessions[session_id] = time.time() def expire_session(self, session_id): with self._lock: if session_id in self._sessions: del self._sessions[session_id] keys_to_remove = [ k for k, v in self._nodes.items() if v['session'] == session_id ] for k in keys_to_remove: del self._nodes[k] def create_ephemeral_sequential(self, path, session_id): with self._lock: self._counter += 1 node_name = f"{path}/lock-{self._counter:010d}" self._nodes[node_name] = { 'session': session_id, 'seq': self._counter, 'created': time.time() } return node_name def get_sorted_children(self, path): with self._lock: children = [ (name, info['seq']) for name, info in self._nodes.items() if name.startswith(path + '/lock-') ] children.sort(key=lambda x: x[1]) return [name for name, _ in children] def delete_node(self, node_name): with self._lock: if node_name in self._nodes: del self._nodes[node_name] return True return False class ZKDistributedLock: """基于ZooKeeper模拟的分布式锁""" def __init__(self, zk_sim, path, session_id): self._zk = zk_sim self._path = path self._session_id = session_id self._node_name = None def acquire(self): self._node_name = self._zk.create_ephemeral_sequential( self._path, self._session_id ) children = self._zk.get_sorted_children(self._path) if children and children[0] == self._node_name: return True return False def release(self): if self._node_name: return self._zk.delete_node(self._node_name) return False def run_demo(): print("=" * 60) print("分布式锁演示: Redis SETNX / ZooKeeper临时节点 / Watchdog续期") print("=" * 60) # --- Redis锁演示 --- print("\n[1] Redis SETNX 分布式锁演示") redis = RedisLockSimulator() lock_a = DistributedLock(redis, "order_lock_001", timeout=5) lock_b = DistributedLock(redis, "order_lock_001", timeout=5) print(f" 客户端A获取锁: {lock_a.acquire(blocking=False)}") print(f" 客户端B获取锁: {lock_b.acquire(blocking=False)}") print(f" 客户端A释放锁: {lock_a.release()}") print(f" 客户端B再次获取: {lock_b.acquire(blocking=False)}") lock_b.release() # --- Watchdog续期演示 --- print("\n[2] Watchdog 自动续期演示") redis2 = RedisLockSimulator() lock_w = DistributedLock(redis2, "task_lock", timeout=3) lock_w.acquire(blocking=False) print(f" 锁初始超时时间: 3秒") time.sleep(1) redis2.extend_expire("task_lock", lock_w._client_id, 3) print(f" Watchdog续期成功, 锁仍然有效") lock_w.release() # --- ZooKeeper锁演示 --- print("\n[3] ZooKeeper 临时顺序节点分布式锁演示") zk = ZKLockSimulator() zk.create_session("session_1") zk.create_session("session_2") zk.create_session("session_3") zk_lock1 = ZKDistributedLock(zk, "/locks/mylock", "session_1") zk_lock2 = ZKDistributedLock(zk, "/locks/mylock", "session_2") zk_lock3 = ZKDistributedLock(zk, "/locks/mylock", "session_3") print(f" 客户端1获取锁: {zk_lock1.acquire()}") print(f" 客户端2获取锁: {zk_lock2.acquire()}") print(f" 客户端3获取锁: {zk_lock3.acquire()}") print(f" 客户端1释放锁...") zk_lock1.release() children = zk.get_sorted_children("/locks/mylock") print(f" 剩余节点: {len(children)}") zk_lock2_new = ZKDistributedLock(zk, "/locks/mylock", "session_2") print(f" 客户端2重新获取锁: {zk_lock2_new.acquire()}") zk_lock2_new.release() zk_lock3_new = ZKDistributedLock(zk, "/locks/mylock", "session_3") print(f" 客户端3获取锁: {zk_lock3_new.acquire()}") # --- 会话超时自动释放演示 --- print("\n[4] ZooKeeper 会话超时自动释放演示") zk.expire_session("session_3") children = zk.get_sorted_children("/locks/mylock") print(f" 会话超时后剩余节点数: {len(children)}") print("\n" + "=" * 60) print("演示完成!") print("=" * 60) if __name__ == "__main__": run_demo()
class RedisLockSimulator:定义Redis锁模拟器类,使用字典模拟Redis的key-value存储self._store = {}使用字典模拟Redis的键空间,存储锁的key及其对应的值和过期时间def setnx(self, key, value, ex=None):模拟Redis SETNX命令,仅在key不存在时设置,ex参数控制过期时间if key in self._store:检查key是否已存在,若存在则返回False表示获取锁失败return False锁已被其他客户端持有,返回Falseself._store[key] = {key不存在,创建锁条目,记录持有者标识和过期时间'value': value,存储客户端唯一标识(UUID),用于释放锁时的归属验证'expire': time.time() + ex if ex else None计算锁的绝对过期时间,防止死锁的兜底机制return True设置成功,返回True表示获取锁成功def delete(self, key, value):模拟释放锁操作,原子性地验证锁归属并删除if key in self._store and self._store[key]['value'] == value:验证锁的持有者是否为当前客户端,防止误删他人的锁del self._store[key]归属验证通过,删除锁key完成释放def extend_expire(self, key, value, additional_time):模拟锁续期操作,Watchdog机制的核心方法class DistributedLock:基于Redis模拟器的分布式锁封装,包含Watchdog自动续期功能def acquire(self, blocking=True, retry_interval=0.3):获取锁方法,支持阻塞等待和非阻塞两种模式acquired = self._redis.setnx(调用SETNX尝试获取锁,设置客户端标识和超时时间self._name, self._client_id, ex=self._timeout锁名为资源标识,client_id为UUID,timeout为自动过期时间if acquired:SETNX成功表示获取锁成功self._is_locked = True标记当前锁状态为已持有self._start_watchdog()启动Watchdog后台线程,定期续期防止锁过期def _watchdog_loop(self):Watchdog循环:每timeout/3时间检查并续期锁interval = self._timeout / 3续期间隔设为超时时间的1/3,确保在过期前完成续期self._redis.extend_expire(调用续期方法延长锁的过期时间class ZKLockSimulator:定义ZooKeeper锁模拟器,模拟临时顺序节点机制def create_ephemeral_sequential(self, path, session_id):模拟创建临时顺序节点,返回节点名称self._counter += 1全局递增计数器,模拟ZooKeeper的顺序节点编号node_name = f"{path}/lock-{self._counter:010d}"生成带序号的节点名称,序号保证全局唯一递增def expire_session(self, session_id):模拟会话超时,自动删除该会话创建的所有临时节点class ZKDistributedLock:基于ZooKeeper模拟器的分布式锁封装def acquire(self):获取锁:创建临时顺序节点后检查是否为最小序号children = self._zk.get_sorted_children(self._path)获取所有子节点并按序号排序if children and children[0] == self._node_name:如果当前节点序号最小则获取锁成功def run_demo():演示入口:依次展示Redis锁、Watchdog续期、ZK锁、会话超时等核心机制
常见问答
为什么分布式锁需要设置超时时间?如果不设置会有什么后果?
Redlock算法的核心思想是什么?为什么存在争议?
ZooKeeper和etcd实现分布式锁的机制有何异同?
在高并发场景下,如何优化分布式锁的性能?
如何测试分布式锁实现的正确性?
常见误区
误区
正解Redis分布式锁在主从切换场景下可能丢失锁信息,不适合对互斥性有严格要求的场景。需要强一致性保证时应选择etcd或ZooKeeper等CP系统。
误区
正解锁超时时间应设置为预期执行时间的合理倍数,配合Watchdog自动续期机制,而非一味增大超时时间。
误区
正解获取分布式锁后仍需进行必要的状态检查(如版本号校验、幂等性检查),分布式锁只保证互斥访问,不保证数据状态的一致性。
误区
正解并非所有分布式锁实现都保证公平性。ZooKeeper天然支持公平锁,Redis默认是非公平锁,需要根据业务需求选择合适的实现。
参考来源
- webpageRedis Distributed LocksRedis官方文档关于分布式锁的实现模式,包括SETNX、Redlock算法等
- webpageZooKeeper RecipesZooKeeper官方文档中的Recipes章节,包含分布式锁、Leader选举等经典实现方案
- webpageetcd API Referenceetcd v3.5 API参考文档,包含Lease、Txn、KV等分布式锁相关的API说明
- webpageHow to do distributed lockingMartin Kleppmann关于分布式锁的经典文章,分析了Redlock算法的安全性问题