分布式锁原理与Redis、Zookeeper实现对比

发布时间:2026/11/4 8:58:19
分布式锁原理与Redis、Zookeeper实现对比 1. 分布式锁的本质与核心诉求在分布式系统中多个服务实例对共享资源的并发访问控制是个经典难题。去年我们电商系统就遇到过这样的场景大促期间库存超卖事后排查发现是多个节点同时判断有库存后执行了扣减操作。这时候就需要一个跨JVM的互斥机制——这就是分布式锁要解决的核心问题。分布式锁与单机锁的本质区别在于其分布式特性。单机环境下用synchronized或ReentrantLock就能解决的同步问题在分布式场景下变得复杂起来。我画了个对比表格帮助理解特性单机锁分布式锁作用范围单个JVM进程内跨网络的多节点实现方式内存互斥量外部存储系统性能影响纳秒级毫秒级故障影响进程崩溃自动释放需要特殊机制处理节点宕机一个合格的分布式锁需要满足几个基本要求互斥性同一时刻只有一个客户端能持有锁可重入性同一个客户端可多次获取同一把锁锁超时防止死锁必须有自动释放机制高可用锁服务需要具备容错能力非阻塞获取锁失败应有快速失败机制2. Redis分布式锁的演进之路2.1 第一代SETNXEXPIRE方案最早的Redis锁实现非常简单// 加锁 Boolean result redis.setnx(lock_key, value); if(result) { redis.expire(lock_key, 30); } // 解锁 redis.del(lock_key);这个方案有三个致命缺陷加锁和设置过期时间是两个独立操作不是原子的锁过期时间评估不准可能导致提前释放直接删除可能误删其他客户端的锁我在生产环境就遇到过这样的案例某个耗时任务执行到一半锁过期被其他节点获取导致数据不一致。后来我们通过添加客户端唯一标识解决了部分问题String clientId UUID.randomUUID().toString(); Boolean result redis.set(lock_key, clientId, NX, EX, 30);2.2 第二代Redlock算法Redis作者Antirez提出了Redlock算法核心思想是获取当前时间依次向N个Redis节点获取锁计算获取锁总耗时只有多数节点获取成功且耗时小于锁有效期才认为成功锁的实际有效时间 初始有效时间 - 获取锁耗时这个算法在Redis集群环境下提供了更好的可靠性。但Martin Kleppmann指出其仍然存在时钟漂移问题如果某个Redis节点时钟发生跳跃可能导致锁提前释放。2.3 生产环境中的最佳实践经过多次迭代我们团队现在采用的方案是// 加锁 String result redis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); // 解锁 String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));关键改进点使用原子命令保证加锁和过期时间设置的原子性解锁时使用Lua脚本保证检查和解锁的原子性每个客户端使用唯一ID标识自己3. Redisson分布式锁实现解析3.1 可重入锁的实现原理Redisson的RLock实现了java.util.concurrent.locks.Lock接口其核心加锁逻辑如下T RFutureT tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommandT command) { internalLockLeaseTime unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, if (redis.call(exists, KEYS[1]) 0) then redis.call(hset, KEYS[1], ARGV[2], 1); redis.call(pexpire, KEYS[1], ARGV[1]); return nil; end; if (redis.call(hexists, KEYS[1], ARGV[2]) 1) then redis.call(hincrby, KEYS[1], ARGV[2], 1); redis.call(pexpire, KEYS[1], ARGV[1]); return nil; end; return redis.call(pttl, KEYS[1]);, Collections.ObjectsingletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }这段Lua脚本实现了使用Hash结构存储锁信息field为客户端IDvalue为重入次数首次获取锁时设置过期时间同一客户端再次获取时增加重入计数返回锁的剩余生存时间3.2 看门狗机制解析Redisson有个精妙的看门狗机制解决锁续期问题private void scheduleExpirationRenewal(final long threadId) { if (expirationRenewalMap.containsKey(getEntryName())) { return; } Timeout task commandExecutor.getConnectionManager().newTimeout(new TimerTask() { Override public void run(Timeout timeout) throws Exception { RFutureBoolean future commandExecutor.evalWriteAsync(...); future.addListener(new FutureListenerBoolean() { Override public void operationComplete(FutureBoolean future) throws Exception { expirationRenewalMap.remove(getEntryName()); if (!future.isSuccess()) { return; } if (future.getNow()) { scheduleExpirationRenewal(threadId); } } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); if (expirationRenewalMap.putIfAbsent(getEntryName(), task) ! null) { task.cancel(); } }关键点后台线程每1/3锁过期时间执行一次续期只有持有锁的客户端才会续期客户端正常释放锁时会取消续期任务3.3 生产环境配置建议根据我们的压测经验给出以下配置建议# application.yml redisson: lock: # 默认锁过期时间 default-lease-time: 30000 # 获取锁等待时间 wait-time: 10000 # 看门狗检查间隔 lock-watchdog-timeout: 30000注意事项不要设置过长的leaseTime建议30秒左右业务代码执行时间不应超过leaseTime获取锁失败时应合理设置重试策略4. Zookeeper分布式锁深度解析4.1 基于临时顺序节点的实现Zookeeper实现分布式锁的核心是利用了临时顺序节点的特性每个客户端在锁目录下创建临时顺序节点获取目录下所有子节点判断自己是否是最小节点如果是则获取锁否则监听前一个节点当前一个节点被删除时重新执行判断示例代码public void lock() throws Exception { ourPath zk.create(root /lock-, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); while(true) { ListString children zk.getChildren(root, false); Collections.sort(children); if(ourPath.endsWith(children.get(0))) { return; } else { String prevNode root / children.get(Collections.binarySearch(children, ourPath.substring(ourPath.lastIndexOf(/) 1)) - 1); CountDownLatch latch new CountDownLatch(1); Stat stat zk.exists(prevNode, new Watcher() { public void process(WatchedEvent event) { latch.countDown(); } }); if(stat ! null) { latch.await(); } } } }4.2 ZK锁与Redis锁的对比维度Redis锁ZK锁性能更高(毫秒级)较低(百毫秒级)可靠性依赖Redis持久化基于ZK的强一致性实现复杂度较简单较复杂特殊特性支持RedLock支持公平锁适用场景高性能要求的非关键业务强一致性要求的核心业务4.3 生产环境中的注意事项要处理ConnectionLoss等异常情况合理设置sessionTimeout防止误删锁建议使用Curator框架而非原生API监控ZK集群健康状态5. 分布式锁的典型问题与解决方案5.1 锁过期与业务执行时间矛盾这是我们遇到最多的问题业务代码执行时间超过锁过期时间。解决方案有合理评估锁超时时间留足buffer实现锁续期机制如Redisson的看门狗将大事务拆分为小事务5.2 锁误删问题案例客户端A获取锁后阻塞锁超时后被客户端B获取此时A恢复执行并误删B的锁。解决方案每个客户端使用唯一标识解锁时校验标识Lua脚本实现5.3 集群脑裂问题Redis哨兵或集群发生脑裂时可能导致多个客户端同时获取锁。解决方案使用Redlock等多节点方案业务层做幂等处理关键业务使用ZK等强一致性方案5.4 性能优化建议减少锁粒度按业务维度拆分锁使用读写锁替代互斥锁本地缓存分布式锁结合避免在锁内执行耗时操作6. 选型建议与架构思考经过多个项目的实践我的选型建议是对性能要求高且允许少量不一致Redis锁强一致性要求的核心业务ZK锁超高并发场景考虑分段锁或乐观锁架构设计时需要思考是否真的需要分布式锁能否用消息队列或CAS替代锁的粒度是否合理能否按业务维度拆分是否有完善的监控和告警机制最后分享一个真实案例我们曾经用Redis锁实现了一个全局配置更新功能后来发现其实用版本号CAS就能实现性能提升了10倍。所以记住分布式锁是最后的解决方案不是首选方案。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询