本地锁在微服务中完全失效,因其仅限单jvm进程内有效,跨实例无法感知锁状态,且崩溃后不自动释放、无全局标识;redis分布式锁需用set nx px原子命令加锁、lua脚本校验owner解锁,zookeeper适合强一致性场景。

本地锁为什么在微服务里完全失效
因为本地锁(比如 synchronized 或 ReentrantLock)只在单个 JVM 进程内有效,它的“锁状态”存在内存里,线程靠抢 Monitor 或 AQS 队列排队——但这个队列其他机器上的线程根本看不见。
- 两个 Spring Boot 实例部署在不同服务器上,都调用
updateOrderStatus(),哪怕方法里加了synchronized,照样会同时更新同一条数据库记录 - 本地锁的持有者崩溃后,锁不会自动释放(JVM 一挂,Monitor 就没了),而分布式系统里这恰恰是常态
- 没有全局唯一标识:线程 A 在机器1上锁了
order:1001,线程 B 在机器2上根本不知道这事,它会直接覆盖写
Redis 分布式锁最简可用写法(别再用 setIfAbsent 单独设值)
用 setIfAbsent("lock:order:1001", "client-uuid", 30, TimeUnit.SECONDS) 看似简单,但这是错的——加锁和设过期时间不是原子操作,中间若 JVM 挂掉,锁就永久存在了。
- 正确姿势必须用 Redis 原生命令:
SET lock:order:1001 client-uuid NX PX 30000,NX 和 PX 同时生效才保证互斥+防死锁 - Java 中推荐用
redisTemplate.opsForValue().set("lock:order:1001", "client-uuid", Duration.ofSeconds(30), RedisSetOption.SET_IF_ABSENT)(Spring Data Redis 2.7+ 支持) - 千万别用
expire()单独设超时:网络延迟或命令重排可能导致 key 已设但 expire 失败
解锁必须校验 owner,否则删错别人的锁
常见错误是:加锁用 UUID,解锁却直接 del lock:order:1001。一旦业务执行超时、锁自动过期,而这时另一个客户端刚拿到锁,你的 del 就把人家的锁干掉了。
- 解锁必须用 Lua 脚本保证原子性:
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:order:1001 client-uuid - Redisson 的
RLock.unlock()内部就是这么做的,自己手写别省这一步 - 如果不用 Lua,至少得用
GET + DEL两步,但中间可能被其他客户端抢先续期或重入,不可靠
什么时候该选 ZooKeeper 而不是 Redis 锁
Redis 锁快,但主从异步复制下,master 写入成功后宕机、slave 升为 master,旧锁可能丢失——这就是 RedLock 试图解决的问题,但实际运维成本高、争议大。
- ZooKeeper 临时顺序节点天然支持强一致性:连接断开,锁自动释放;watcher 机制能实时通知等待者,适合对一致性要求极高的场景(如金融核心账务)
- 但 ZooKeeper 写性能远低于 Redis,频繁争锁时容易成为瓶颈;而且客户端需维护 session,超时时间配置不当会导致假释放
- 如果你的系统已重度依赖 ZooKeeper 做服务发现,顺手用它做锁还行;否则,优先用 Redisson 封装好的看门狗+重试机制更稳妥










