synchronized在集群中失效,因其是JVM级单机锁,仅对同一进程内线程互斥,无法跨多个JVM实例协调;必须借助Redis等外部组件实现分布式锁,满足互斥性、防死锁和容错性。

Java 微服务在多实例部署时,单机锁(如 synchronized 或 ReentrantLock)完全失效,因为各实例运行在不同 JVM 中,彼此内存隔离。要保证互斥,必须依赖一个所有实例都能共同访问、且操作具备原子性的**外部协调组件**。核心思路是:把“谁持有锁”这个状态从进程内搬到进程外,由第三方服务统一裁决。
必须依赖共享的、原子性可控的锁资源
本地锁管不了其他 JVM,所以得找一个所有微服务实例都能连上的中心化存储。主流选择有 Redis、ZooKeeper、etcd 或数据库。其中 Redis 因其高性能、支持原子命令(如 SET key value NX EX)、天然适合高并发场景,成为最常用方案。
- NX 确保键不存在才设置成功——实现“抢占式加锁”,避免多个实例同时抢到
- EX 指定过期时间(如 30 秒)——防止客户端宕机导致锁永远不释放(防死锁)
- 值建议设为唯一标识(如 UUID 或服务实例 ID + 时间戳),便于后续校验和安全释放
不能只 SETNX,还得解决释放安全问题
简单用 SETNX + DEL 存在风险:加锁成功后业务出错或机器宕机,没来得及 DEL,锁就一直挂着。更危险的是,A 实例锁超时自动释放后,B 实例拿到锁开始执行,此时 A 恢复并误删了 B 的锁(因为 DEL 不校验所有权)。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 推荐用 Lua 脚本做原子性解锁:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - 每次解锁前比对锁值,只允许自己释放自己持有的锁
- Spring Boot 中可封装为
unlock()方法,内部调用redisTemplate.execute(script, ...)
生产环境建议直接用 Redisson,别手写基础逻辑
Redisson 是经过大规模验证的 Redis 客户端,它把分布式锁的复杂性封装好了,开箱即用:
立即学习“Java免费学习笔记(深入)”;
- 自动看门狗机制:锁快过期时,后台线程会自动续期(只要客户端还活着),避免业务执行久导致误释放
- 可重入:同一线程/实例多次
lock()不会阻塞,计数管理透明 - 支持等待时间(
tryLock(waitTime, leaseTime, TimeUnit))和重试策略,应对瞬时竞争 - 提供公平锁、读写锁、联锁等高级能力,适配更多业务场景
兜底与可观测性不能少
再好的锁机制也需配合运维手段:
- 锁 Key 命名要有业务上下文和环境标识,例如
order:pay:lock:prod:10023,方便排查和清理 - 关键锁操作打日志(含实例 ID、锁 Key、耗时、是否成功),结合链路追踪定位瓶颈
- 监控 Redis 中锁 Key 的数量、平均 TTL、异常删除率,及时发现长持有或泄漏
- 对非强一致性场景,考虑用最终一致性+幂等设计替代强锁,降低系统耦合

















