
一、引言在分布式系统中多个服务实例需要访问共享资源时传统的单机锁如 Java 中的 synchronized、ReentrantLock无法跨进程生效。分布式锁正是为了解决这一跨进程互斥问题而诞生的。本文将从实现机制、原理、代码示例和优缺点四个维度系统梳理高并发场景下几种主流的分布式锁实现方案并在最后给出横向对比。redis常用命令参考有道云笔记 01-Redis命令参考手册完整版.pdf二、单机锁的局限为什么需要分布式锁在单机应用中synchronized 和 ReentrantLock 可以很好地保证线程安全。但在分布式架构下多个 JVM 进程运行在不同的机器上每个进程内部的锁只能约束本进程的线程无法阻止其他进程同时访问共享资源。因此需要一种能够跨进程、跨节点生效的互斥机制这就是分布式锁。三、基于 Redis 的分布式锁Redis 凭借其高性能和丰富的数据结构成为目前最流行的分布式锁实现载体。3.1 实现原理利用 Redis 的 SETNXSET if Not eXists命令在键不存在时设置成功从而获得锁。为避免持有锁的实例宕机导致死锁需要设置过期时间。释放锁时使用 Lua 脚本校验持有者身份防止误删他人锁。3.2 基础代码示例SETNX 过期时间RequestMapping(/deduct_stock) public String deductStock() { String lockKey lock:product_101; String clientId UUID.randomUUID().toString(); //Boolean result stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId ); // 加锁 //stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS); //设置锁超时时间 Boolean result stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v) 保证原子性操作加锁、设置超时时间 if (!result) { return error_code; } try { int stock Integer.parseInt(stringRedisTemplate.opsForValue().get(stock)); // jedis.get(stock) if (stock 0) { int realStock stock - 1; stringRedisTemplate.opsForValue().set(stock, realStock ); // jedis.set(key,value) System.out.println(扣减成功剩余库存: realStock); } else { System.out.println(扣减失败库存不足); } } finally { if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) { stringRedisTemplate.delete(lockKey); } } return end; }3.3 优缺点分析优点性能极高基于内存操作实现相对简单支持锁自动过期避免死锁。缺点锁过期时间难以精确把控业务执行超时会导致锁提前释放Redis 主从切换时可能出现锁丢失需要额外维护 Redis 集群的高可用。存在问题当线程1最后执行finally代码块释放锁时如果刚好if判断true后此时锁超时时间到期自动释放锁。此时线程2进来后刚获取到锁线程1释放锁的代码继续执行则可能会释放线程2的锁。四、基于 Redisson 的分布式锁Redisson 是 Redis 官方推荐的 Java 客户端封装了完善的分布式锁实现解决了原生 Redis 锁的诸多痛点。4.1 实现原理Redisson 分布式锁的核心机制包括两部分Lua 脚本保证加锁、解锁操作的原子性看门狗机制Watchdog自动续期避免业务未执行完锁就被释放。Lua脚本执行特点一段代码打包依次执行减少网络开销与管道类似。执行过程中不会被其他命令打断/插入保证原子性。代替Redis的事务功能比Redis自带的事务更简洁、高效。加锁时Redisson 通过 Lua 脚本判断锁是否存在若不存在则设置锁并记录持有者线程 ID同时设置默认过期时间30 秒。看门狗会每隔 10 秒检查一次若锁仍被持有则自动将过期时间重置为 30 秒确保长任务执行期间锁不会提前失效。4.2 代码示例Autowired private Redisson redisson; RequestMapping(/deduct_stock) public String deductStock() { String lockKey lock:product_101; //获取锁对象 RLock redissonLock redisson.getLock(lockKey); //加分布式锁 redissonLock.lock(); // .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); try { int stock Integer.parseInt(stringRedisTemplate.opsForValue().get(stock)); // jedis.get(stock) if (stock 0) { int realStock stock - 1; stringRedisTemplate.opsForValue().set(stock, realStock ); // jedis.set(key,value) System.out.println(扣减成功剩余库存: realStock); } else { System.out.println(扣减失败库存不足); } } finally { //解锁 redissonLock.unlock(); } return end; }4.3 看门狗机制详解看门狗是 Redisson 解决锁过期问题的核心设计。默认情况下锁的过期时间为 30 秒默认30秒可设置超时时间一般不建议修改。当客户端成功获取锁后后台会启动一个定时任务每 10 秒过期时间/3执行一次续期操作将锁的过期时间重新设置为 30 秒。这样只要业务线程还在运行锁就不会因为超时而被 Redis 自动删除。当业务执行完毕调用 unlock 时看门狗任务会被取消锁被正常释放。4.4 优缺点分析优点看门狗机制有效避免业务超时导致锁提前释放Lua 脚本保证原子性支持可重入锁、公平锁、读写锁等多种模式API 友好接入成本低。缺点仍然依赖 Redis 的高可用主从切换极端场景下可能丢失锁看门狗续期会带来一定的额外网络开销。五、分布式锁实现对比总结实现方案一致性性能可靠性实现复杂度运维成本适用场景数据库锁弱低中低低低并发、简单业务Redis SETNX弱高中中中高并发、可容忍极端丢失Redisson中高较高低中高并发、业务执行时间长综合来看Redisson 分布式锁在性能、可靠性和易用性之间取得了较好的平衡是目前高并发业务中最常用的方案。数据库锁则更适合低并发、对性能不敏感的简单场景。重点Redisson源码解析、看门狗机制实现锁到期自动续命及定时任务检查锁状态。六、面试常见问题与解答6.1 基础概念类什么是分布式锁为什么单机锁synchronized、ReentrantLock无法满足分布式场景的需求分布式锁是用于在分布式系统中跨进程、跨节点保证共享资源互斥访问的机制。单机锁如 synchronized、ReentrantLock只能约束单个 JVM 进程内的线程无法阻止运行在不同机器上的多个进程同时访问共享资源因此需要分布式锁。分布式锁需要满足哪些基本特性主要包括互斥性同一时刻只有一个客户端持有锁、可重入性同一线程可重复获取、防死锁锁必须能自动过期释放、高性能加解锁开销低、高可用锁服务不因单点故障而不可用。分布式锁的实现方案有哪些请列举并简要说明。常见方案包括数据库锁基于唯一索引或悲观锁、Redis 分布式锁SETNX 过期时间、Redisson 分布式锁Lua 脚本 看门狗、ZooKeeper 分布式锁临时顺序节点。6.2 Redis 分布式锁类Redis 实现分布式锁的核心命令是什么SETNX 和 SET NX EX 有什么区别核心命令是 SETNXSET if Not eXists。SETNX 只能单独设置键无法同时设置过期时间而 SET NX EX 可以在一条命令中同时完成加锁和设置过期时间保证原子性避免加锁后宕机导致死锁。为什么加锁和设置过期时间必须保证原子性如果不这样做会有什么问题如果先加锁再单独设置过期时间两步之间若进程崩溃锁将永远不会被释放造成死锁。使用 SET NX EX 或 Lua 脚本将两步合并为原子操作可避免该问题。释放锁时为什么要用 Lua 脚本校验持有者身份直接 delete 会有什么风险直接 delete 可能误删其他线程的锁。例如线程1的锁超时自动释放后线程2获取了同一把锁此时线程1执行 delete 会释放线程2的锁。通过 Lua 脚本先比对 value持有者标识再删除可确保只有持有者才能释放自己的锁。Redis 分布式锁的过期时间如何设置业务执行时间超过过期时间会怎样过期时间需要根据业务最大执行时长合理设置一般留出一定余量。若业务执行时间超过过期时间锁会被 Redis 自动释放其他线程可获取锁导致并发安全问题。Redis 主从架构下分布式锁可能丢失如何理解这个问题当主节点写入锁后还未同步到从节点主节点发生故障切换从节点晋升为主节点时锁数据可能丢失导致其他客户端也能获取同一把锁破坏互斥性。6.3 Redisson 分布式锁类Redisson 相比原生 Redis 分布式锁解决了哪些痛点主要解决三个痛点一是通过 Lua 脚本保证加锁、解锁的原子性二是通过看门狗机制自动续期避免业务超时锁被提前释放三是提供可重入锁、公平锁、读写锁等多种模式API 更友好。Redisson 的看门狗Watchdog机制是什么默认续期时间是多少它是如何工作的看门狗是 Redisson 自动续期的机制。默认锁过期时间为 30 秒客户端获取锁后后台启动定时任务每 10 秒过期时间/3检查一次若锁仍被持有则自动将过期时间重置为 30 秒确保长任务执行期间锁不失效。看门狗机制能完全避免锁提前释放的问题吗为什么不能完全避免。看门狗依赖客户端与 Redis 之间的网络通信若客户端长时间 GC 停顿、网络分区或续期失败锁仍可能提前释放。极端情况下仍需结合业务幂等或 RedLock 等方案增强可靠性。Redisson 的 Lua 脚本在加锁、解锁中起到什么作用为什么能保证原子性Lua 脚本将多条 Redis 命令打包成一段脚本一次性执行执行过程中不会被其他命令打断从而保证原子性。加锁时判断锁是否存在并设置持有者解锁时校验持有者身份再删除都通过 Lua 脚本完成。Redisson 支持哪些锁模式可重入锁、公平锁、读写锁分别适用于什么场景支持可重入锁同一线程可重复获取适合递归或嵌套调用、公平锁按请求顺序获取适合需要公平性的场景、读写锁读读共享、读写互斥适合读多写少的场景。6.4 方案对比与选型类Redis 分布式锁和 ZooKeeper 分布式锁在一致性、性能上有什么区别ZooKeeper 基于临时顺序节点和 Watcher 机制一致性更强能避免锁丢失问题但性能相对较低Redis 分布式锁性能高、实现简单但在主从切换等极端场景下可能丢失锁一致性较弱。为什么说 Redisson 在高并发业务中更常用它的优势和局限分别是什么Redisson 在性能、可靠性和易用性之间取得较好平衡性能高、看门狗避免锁提前释放、Lua 脚本保证原子性、API 友好。局限是仍依赖 Redis 高可用主从切换极端场景可能丢锁看门狗续期带来额外网络开销。在金融、交易等对一致性要求极高的场景你会选择哪种分布式锁为什么优先选择 ZooKeeper 分布式锁或 RedLock 方案。这类场景对一致性要求极高ZooKeeper 的临时顺序节点机制能提供更强的互斥保证避免 Redis 主从切换导致的锁丢失问题。数据库分布式锁的优缺点是什么适合什么场景优点是实现简单、无需额外组件、可靠性较高缺点是性能低、存在单点风险、锁释放依赖事务提交。适合低并发、对性能不敏感的简单业务场景。如果让你设计一个分布式锁你会考虑哪些关键点会重点考虑互斥性同一时刻只有一个持有者、防死锁自动过期、可重入性、加解锁原子性、持有者身份校验防止误删、高可用避免单点故障、以及业务超时后的续期或降级策略。6.5 场景与实战类在秒杀、库存扣减等场景中如何用 Redisson 实现分布式锁请描述代码流程。流程为获取锁对象 RLock调用 lock() 加锁执行业务逻辑读取库存、判断是否充足、扣减库存finally 中调用 unlock() 释放锁。加锁后看门狗自动续期确保业务执行期间锁不失效。如果 Redisson 的看门狗续期失败锁提前释放业务还在执行会有什么后果如何规避后果是其他线程可获取锁并同时操作共享资源造成并发安全问题。规避方式包括业务逻辑设计为幂等、使用 RedLock 多节点加锁、或对关键操作增加版本号/乐观锁校验。如何对分布式锁进行压测你会关注哪些指标压测主要关注加锁/解锁的吞吐量QPS、平均耗时与 P99 延迟、锁竞争时的等待时间、以及并发下是否出现锁丢失或误删。可通过多线程模拟高并发请求观察库存扣减是否准确、是否出现超卖。线上出现锁误删、锁失效等问题你会如何排查和定位排查思路先确认锁的 key 和 value 是否唯一标识持有者检查是否所有释放路径都经过 Lua 脚本校验查看 Redis 日志确认锁是否被提前过期结合业务日志定位是否出现看门狗续期失败或网络抖动必要时增加监控告警统计锁竞争、续期失败等指标。