Redis 分布式锁学习笔记
一、为什么需要分布式锁?
Java 的 synchronized 和 ReentrantLock 属于 JVM 本地锁,只能保证单个 JVM 内的线程安全。
在 Spring Boot 集群部署时,每个 JVM 都有独立的锁对象,无法协调不同服务实例的并发操作。
因此,需要使用分布式锁实现跨 JVM 的互斥。
常见实现方式:MySQL、Redis、ZooKeeper。
二、分布式锁的核心特性
多进程可见和互斥性是基础,其次考虑安全性、高可用和高性能。
另外,可重入性表示同一线程能够重复获取自己持有的锁,属于常见特性,但并非所有分布式锁都必须支持。
三、分布式锁的三种实现方式
1. MySQL
利用数据库事务和行锁实现互斥。
SELECT * FROM goods WHERE id = 1 FOR UPDATE;其他事务尝试获取同一条记录的排他锁时,需要等待。
事务提交、回滚或连接异常终止并完成回滚后,相关锁会释放。
注意:普通 UPDATE 虽然会进行加锁,但不等于自动实现了完整的业务分布式锁。
2. Redis
通过 SET NX PX 实现互斥锁。
SET lock:order:1001 uuid123 NX PX 30000NX:锁不存在时才能创建。PX:设置过期时间,避免永久占用。uuid123:锁的唯一标识,用于判断锁的归属。
Redis 分布式锁需要解决两个问题:
① 锁提前过期
业务尚未执行完毕,锁已经过期,其他线程就可能获得同一把锁。
解决方案:使用 Redisson 的 Watchdog(看门狗)机制自动续期。
注意:Redisson 只有在不指定固定 leaseTime 的相应加锁方式下,才使用看门狗自动续期。
② 误删其他线程的锁
线程 A 的锁过期后,线程 B 获取了同一把锁。如果线程 A 直接执行 DEL,就可能误删线程 B 的锁。
解决方案:使用唯一标识配合 Lua 脚本,原子地判断锁归属并释放。
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0此外,Redis 主从异步复制与故障切换也可能导致锁状态丢失,不能认为设置 TTL 就能保证所有情况下的互斥安全。
3. ZooKeeper
通过临时顺序节点实现分布式锁。
例如:
线程 A → lock-0001
线程 B → lock-0002
线程 C → lock-0003序号最小的节点获得锁。
其他线程监听自己的前一个节点。
前一个节点删除后,后继线程继续竞争锁。
客户端会话过期时,对应临时节点自动删除。
ZooKeeper 适合分布式协调场景,但节点操作和协调成本通常高于 Redis 的简单锁操作。
四、一人一单如何实现?
秒杀场景下,同一用户可能同时发送多个请求。
如果多个线程都查询到用户尚未下单,就可能重复创建订单。
解决方案:按照用户 ID + 商品 ID 加锁,保证同一用户购买同一商品时的操作互斥。
1. 锁必须覆盖事务提交
错误做法:在事务方法内部获取和释放锁。
因为方法内部释放锁后,Spring 事务代理可能还没有提交事务,其他线程就可能查询不到刚创建的订单。
推荐流程:
获取用户分布式锁
↓
调用 Spring 事务方法
↓
检查是否重复下单
↓
扣减库存、创建订单
↓
数据库事务提交
↓
释放分布式锁2. 事务方法必须通过 Spring 代理调用
Spring 的 @Transactional 通常通过 AOP 代理实现。
// 不推荐:同类内部调用可能绕过事务代理
this.createOrder();推荐将锁逻辑和事务逻辑分别放在两个 Spring Service 中,由加锁服务调用注入的事务服务。
这样既能使 @Transactional 生效,也更容易保证锁覆盖整个事务提交过程。
3. 数据库唯一索引兜底
即使使用分布式锁,也建议建立联合唯一索引:
ALTER TABLE voucher_order
ADD UNIQUE KEY uk_user_voucher (user_id, voucher_id);防止同一个用户对同一商品重复下单。
同时,库存扣减应使用原子条件更新,并和订单插入放在同一数据库事务中。
五、实际开发建议
Java 8 + Spring Boot 项目中,推荐直接使用 Redisson,而不是自行实现分布式锁。
主要原因:
支持可重入锁。
支持看门狗自动续期。
提供锁超时与等待机制。
简化加锁、解锁及并发控制代码。
但 Redisson 也不能保证所有故障情况下都不会发生锁失效,关键业务仍需要数据库约束、事务和幂等机制兜底。
六、总结
本地锁:只能保护单个 JVM,多实例部署无法直接共享。
MySQL 分布式锁:利用事务与行锁实现互斥。
Redis 分布式锁:利用
SET NX PX实现互斥,通过唯一标识避免误删,Redisson 可自动续期。ZooKeeper 分布式锁:利用临时顺序节点和监听机制。
一人一单:用户维度加锁,锁覆盖事务提交,通过数据库唯一索引兜底。
核心原则:分布式锁负责跨实例互斥,数据库事务负责数据一致性,唯一约束负责防止重复数据。
