一、分布式锁场景
分布式锁主要解决分布式系统中多服务实例共享资源竞争的问题,在单体应用中本地锁(如synchronized、ReentrantLock)可保证线程安全,但分布式环境下(多 JVM、多节点),本地锁无法跨实例生效,此时需要分布式锁。常见场景包括:
- 库存扣减:秒杀、促销活动中,多个服务实例同时扣减同一商品库存,需防止超卖;
- 分布式任务调度:避免多个节点重复执行同一定时任务(如数据同步、报表生成);
- 分布式事务:确保跨服务的原子操作(如转账时 “扣钱” 和 “加钱” 需同时成功或失败);
- 缓存更新:防止缓存击穿(多个线程同时请求失效的缓存,导致数据库压力骤增)。
二、分布式锁特点
分布式锁需满足以下核心特性,才能保证在分布式环境下有效工作:
- 互斥性:同一时间只能有一个服务实例获取锁,确保共享资源操作的唯一性;
- 安全性:避免死锁(如服务崩溃后锁无法释放),需支持自动释放机制;
- 高可用:锁服务本身需高可用(避免单点故障),获取 / 释放锁的过程不能成为系统瓶颈;
- 可重入性:同一服务实例可多次获取同一把锁(如递归调用场景),避免自己阻塞自己;
- 公平性(可选):按请求顺序获取锁,防止 “饥饿”(部分请求长期无法获取锁);
- 高性能:获取 / 释放锁的操作需高效(低延迟、高并发支持),减少对业务的影响。
三、分布式锁得实现
在 SpringBoot 中,分布式锁的实现通常依赖中间件,主流方案包括Redis、ZooKeeper、数据库三种,各有优劣:
1. 基于 Redis 的分布式锁
原理:利用 Redis 的SET命令原子性(NX- 不存在则设置,PX- 过期时间)实现锁的获取,通过 Lua 脚本原子性删除实现锁的释放。
获取锁:
执行命令 SET lock_key unique_value NX PX 30000(unique_value为服务实例唯一标识,如 UUID;PX 30000表示 30 秒后自动释放,防止死锁)。
若返回OK,则表示获取锁成功;否则失败。
释放锁:
需先判断锁的unique_value是否为当前实例,再删除(避免误删其他实例的锁),通过 Lua 脚本保证原子性:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
SpringBoot 集成:
推荐使用Redisson客户端,它封装了分布式锁的实现,支持可重入(RLock)、自动续期(看门狗机制)、公平锁等特性,无需手动处理过期时间:
@Autowired
private RedissonClient redissonClient;
public void doWithLock() {
RLock lock = redissonClient.getLock("stock:lock:1001"); // 锁的key(如商品ID)
try {
// 尝试获取锁,最多等待10秒,10秒后自动释放
boolean locked = lock.tryLock(10, 10, TimeUnit.SECONDS);
if (locked) {
// 执行业务逻辑(如扣减库存)
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁(需判断是否持有锁,避免异常场景下误释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
优缺点:
优点:性能极高(Redis 读写快),适合高并发场景;
缺点:需处理锁过期问题(若业务执行时间超过过期时间,可能导致锁提前释放),Redisson 的看门狗机制可缓解此问题
2. 基于 ZooKeeper 的分布式锁
原理:利用 ZooKeeper 的临时有序节点和监听机制实现。每个请求创建一个临时有序节点,若自身是最小节点则获取锁;否则监听前一个节点,前一个节点删除后自动竞争锁。
获取锁:
在/lock节点下创建临时有序节点(如/lock/lock-00000001);
获取/lock下所有子节点,判断自身是否为最小节点:是则获取锁;否则监听前一个节点的删除事件。
释放锁:
服务实例释放锁时,删除自身创建的临时节点;若服务崩溃,ZooKeeper 会自动删除临时节点(会话断开),避免死锁。
SpringBoot 集成:
使用Curator框架(ZooKeeper 客户端),其InterProcessMutex类封装了分布式锁实现:
@Autowired
private CuratorFramework curatorFramework;
public void doWithLock() throws Exception {
InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/lock/stock/1001");
try {
// 尝试获取锁,最多等待10秒
boolean locked = lock.acquire(10, TimeUnit.SECONDS);
if (locked) {
// 执行业务逻辑
}
} finally {
// 释放锁
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
}
}
优缺点:
优点:可靠性高(天然支持监听和自动释放),适合对一致性要求高的场景;
缺点:性能低于 Redis(需频繁创建 / 删除节点和网络通信),适合并发量中等的场景。
3. 基于数据库的分布式锁
原理:利用数据库的行锁或唯一约束实现,常见两种方式:
悲观锁(行锁):
通过select ... for update获取行锁,其他事务需等待锁释放。例如:
select * from distributed_lock where lock_key = 'stock:1001' for update;
执行后,其他事务对该记录的操作会阻塞,直到当前事务提交 / 回滚释放锁。
乐观锁(版本号):
基于版本号控制,更新时判断版本号是否匹配,不阻塞但可能重试:
update distributed_lock set count = count - 1, version = version + 1
where lock_key = 'stock:1001' and version = #{currentVersion};
若更新行数为 0,说明版本号已变(被其他事务修改),需重试。
SpringBoot 集成:
通过 MyBatis/JPA 执行上述 SQL,例如用乐观锁
@Update("update distributed_lock set count = count - 1, version = version + 1 where lock_key = #{lockKey} and version = #{version}")
int updateStock(@Param("lockKey") String lockKey, @Param("version") int version);
优缺点:
优点:实现简单(无需额外中间件);
缺点:性能差(悲观锁阻塞,乐观锁可能频繁重试),不适合高并发场景;数据库易成为瓶颈。
总结
实际开发中,需根据场景选择方案:
高并发、高性能优先:选 Redis(Redisson);
高可靠性、一致性优先:选 ZooKeeper(Curator);
简单场景、低并发:可选数据库锁(不推荐高并发)。
在 SpringBoot 中,优先使用成熟客户端(如 Redisson、Curator),避免重复开发锁的细节(如自动续期、异常处理)。
四、










网友评论