在分布式架构下传统的 JVM 锁如synchronized无法跨进程生效。为了保证共享资源如秒杀库存、数据库行记录的并发安全我们需要借助 Redis 实现分布式锁。以下是分布式锁在解决实际问题中的四个关键演进阶段1. 基础实现解决“原子性”与“死锁”最直观的实现是利用 Redis 的SETNXSET if Not Exists命令。初级陷阱如果只执行SETNX加锁一旦客户端宕机锁将永远无法释放导致系统死锁。优化方案必须为锁设置过期时间TTL。同时为了保证“加锁”和“设置过期时间”的原子性避免中间宕机引发死锁应直接使用官方推荐的扩展命令SET key value NX EX seconds即仅当键不存在时设置值并附带过期秒数。2. 安全升级解决“误删锁”进阶陷阱如果业务执行时间超过了锁的过期时间锁会自动释放。此时线程 A 执行完毕后去释放锁可能会误删掉线程 B 刚刚获取到的新锁。优化方案唯一标识加锁时将 Value 设置为唯一标识如UUID ThreadID。Lua 脚本原子删锁释放锁时先校验 Value 是否匹配匹配再删除。这两个动作必须封装在 Lua 脚本中执行确保原子性。3. 高可用挑战解决“主从切换丢锁”架构陷阱在 Redis 主从Master-Slave架构下锁的同步是异步的。如果客户端 A 在主节点加锁成功但主节点还没来得及将数据同步给从节点就宕机了从节点晋升为新主节点后客户端 B 依然能成功加锁。这会导致多个客户端同时持有锁互斥性失效。优化方案引入Redlock红锁算法。摒弃传统的主从架构向多个完全独立的 Redis 主节点同时申请锁只有当超过半数N/2 1的节点加锁成功才算真正获取到分布式锁从而规避单点故障带来的锁丢失风险。4. 生产落地解决“业务超时”与“代码复杂度”终极陷阱业务执行时间难以精准预估固定超时时间容易导致锁提前释放。手写包含 Lua 脚本、唯一标识、Redlock 等逻辑的代码极其复杂且容易遗漏边界情况如锁的可重入性。最终方案直接使用成熟的框架Redisson。看门狗WatchDog机制Redisson 会在后台自动为未执行完的业务续期彻底解决“业务超时导致锁提前释放”的难题。开箱即用它原生支持可重入锁、公平锁、红锁以及底层的 Lua 脚本原子操作是生产环境中低成本、高稳定的标准答案。简单理解为四个阶段阶段一初窥门径单机 SETNX场景单体应用拆分为分布式系统synchronized失效。方案使用 Redis 的SETNX命令。痛点如果业务崩溃锁无法释放死锁。阶段二修补漏洞原子性与防误删演进引入SET key value NX PX原子操作。新问题锁过期了业务还没执行完或者业务执行完了去删除锁结果删掉了别人刚获取的新锁。方案引入唯一ValueUUID Lua 脚本保证“判断-删除”原子性。阶段三高可用挑战Redlock 与 主从一致性演进单点 Redis 不可靠引入主从。新问题异步复制导致的锁丢失主节点挂掉从节点未同步锁。方案探讨 Redlock 算法多节点投票引出其运维复杂性。阶段四成熟落地Redisson 看门狗演进手写代码维护成本太高且难以兼顾性能与可靠性。终局引入Redisson。看门狗WatchDog自动续期解决业务超时问题。可重入基于 Hash 结构记录线程 ID 和重入次数。发布订阅优化锁等待机制减少无效轮询。补充说明加锁前生成唯一 UUID 的含义在分布式系统中锁的 Key 通常使用业务 ID如用户 ID、单据 ID但生成唯一 UUID 仍有其必要性避免锁冲突业务 ID 可能重复使用如订单取消后重新创建。UUID 作为临时唯一标识确保锁的独立性。锁的粒度控制业务 ID 可能对应多个操作如支付和退款。通过拼接 UUID 可细分锁粒度例如order_12345_refund_abcd-uuid。防止误释放线程 A 获取锁后若超时锁可能被自动释放并被线程 B 获取。若线程 A 恢复后直接使用业务 ID 释放锁会误删线程 B 的锁。附加 UUID 可校验锁的归属。业务 ID 作为 Key 的潜在问题锁覆盖风险高并发下相同业务 ID 的多个请求可能竞争同一把锁导致非预期阻塞或锁失效。锁续期混淆自动续期机制可能因业务 ID 重复而错误延长其他线程的锁。推荐实践复合 Key 结构结合业务 ID 和 UUIDlock:user_12345:3a4b5c6d兼顾业务语义与唯一性。锁值设计存储 UUID 作为锁的值释放时校验一致性避免误操作。超时与续约设置合理超时时间并通过 UUID 标识续约归属避免死锁。代码示例Redis 锁// 加锁StringbusinessKeyorder_12345;StringlockIdUUID.randomUUID().toString();booleanlockedredis.set(businessKey,lockId,NX,EX,30);// 释放锁if(lockId.equals(redis.get(businessKey))){redis.del(businessKey);}使用 Redisson 之后你不需要再手动进行 UUID 的比对Redisson 已经在底层帮你完美封装并自动处理了这一切。至于是否需要指定过期时间这与 UUID 的自动比对没有直接关系但会直接影响 Redisson 的另一个核心机制——看门狗Watchdog的自动续期。问题拓展1. 为什么用了 Redisson 就不需要手动比对 UUID 了在手动实现分布式锁时我们需要生成 UUID 并存入 Redis释放锁时再通过 Lua 脚本比对 UUID核心目的是防止“误删他人的锁”。Redisson 在底层已经原生实现了这一整套安全机制自动绑定唯一标识当你调用 Redisson 的加锁方法时它会自动生成一个全局唯一的标识格式通常为UUID:线程ID并将其作为锁的 Value 存入 Redis。自动校验与释放当你调用unlock()释放锁时Redisson 会在内部执行一段封装好的 Lua 脚本。这段脚本会自动去 Redis 中校验当前锁的标识是否属于当前线程。只有校验通过它才会真正删除这把锁。因此使用 Redisson 时你只需要简单地调用lock.lock()和lock.unlock()完全不用操心 UUID 的生成、存储和比对它已经帮你规避了“误删锁”的风险。2. 是否指定过期时间会有什么影响指定过期时间不会改变 Redisson “自动比对 UUID” 的安全特性但它直接决定了看门狗Watchdog机制是否会启动。我们可以分两种情况来看情况一不指定过期时间推荐写法lock.lock()表现当你没有显式指定锁的过期时间时Redisson 会默认给锁设置一个 30 秒的过期时间并自动启动看门狗机制。看门狗的作用看门狗会在后台每隔 10 秒默认 30 秒的 1/3检查一次如果你的业务还没执行完且依然持有这把锁它就会自动把锁的过期时间重新刷新回 30 秒。这完美解决了“业务执行时间超过锁过期时间”导致的并发安全问题。情况二显式指定了过期时间写法lock.lock(10, TimeUnit.SECONDS)表现一旦你手动指定了过期时间比如 10 秒Redisson 就会认为你非常清楚自己的业务耗时此时看门狗机制将不会启动。潜在风险锁会在 10 秒后强制过期释放。如果你的业务逻辑执行超过了 10 秒锁就会被自动释放其他线程可能会抢到这把锁从而引发并发冲突。为了更直观地理解可以参考下表加锁方式看门狗机制适用场景lock.lock()(不指定时间)✅自动启动(默认30s每10s续期)核心业务无法准确预估耗时的场景lock.lock(10, TimeUnit.SECONDS)(指定时间)❌不会启动耗时极短且非常确定的操作如秒杀扣库存