微服务架构中,分布式锁用于保证跨服务实例的互斥操作。基于Redis的分布式锁因其高性能和易部署特性被广泛使用。Spring Boot 3.x配合Redisson客户端,在API友好度和可靠性上都有成熟方案。本文实现一个完整的分布式锁组件,覆盖加锁、解锁、自动续期、异常处理全流程。
Redisson与Jedis实现差异
Jedis实现分布式锁通常用SET key value NX PX命令,需要自己处理续期、重入、释放逻辑。Redisson封装了看门狗(Watchdog)自动续期机制,支持可重入锁、公平锁、读写锁、联锁(MultiLock)多种锁类型。在高并发设计场景下,Redisson的API抽象更贴合业务开发需求。
Spring Boot 3.x集成Redisson
添加Redisson Spring Boot Starter依赖。注意Spring Boot 3.x需要Redisson 3.20.0以上版本以兼容Jakarta EE:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>
application.yml配置:
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password: yourpassword
database: 0
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: "yourpassword"
database: 0
connectionPoolSize: 32
connectionMinimumIdleSize: 8
分布式锁服务封装
封装一个通用的分布式锁工具类,支持超时设置和自动续期:
@Service
public class DistributedLockService {
private final RedissonClient redissonClient;
public DistributedLockService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
/**
* 加锁执行
* @param lockKey 锁key
* @param leaseTime 持锁时间(秒),-1表示启用看门狗自动续期
* @param supplier 业务逻辑
*/
public <T> T executeWithLock(String lockKey, long leaseTime, Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
boolean acquired = false;
try {
acquired = lock.tryLock(0, leaseTime, TimeUnit.SECONDS);
if (!acquired) {
throw new RuntimeException("获取锁失败: " + lockKey);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程中断", e);
} finally {
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
tryLock的第一个参数是等待时间,0表示不等待。leaseTime设为-1时启用看门狗,默认每10秒续期一次,续期到30秒。如果业务执行时间不确定,用-1最安全。如果业务有明确最大执行时间,设置具体leaseTime避免无限续期。
可重入锁与业务场景实现
Redisson的RLock是可重入锁,同一线程可以多次获取同一把锁。适用场景示例——库存扣减:
@Service
public class InventoryService {
private final DistributedLockService lockService;
private final InventoryMapper inventoryMapper;
public boolean deductStock(String skuCode, int quantity) {
String lockKey = "inventory:lock:" + skuCode;
return lockService.executeWithLock(lockKey, 30, () -> {
Inventory inventory = inventoryMapper.selectBySkuCode(skuCode);
if (inventory.getStock() < quantity) {
return false;
}
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
inventoryMapper.insertDeductLog(skuCode, quantity);
return true;
});
}
}
分布式事务场景下,如果方法A调用方法B,两者都需要同一把锁,可重入特性保证不会死锁。Redisson通过Hash结构记录锁持有者线程ID和重入次数,解锁时递减计数。
常见问题排查与解决方案
坑点1:锁释放失败导致永久锁
finally块中直接调用lock.unlock(),如果线程未持有锁(如leaseTime到期自动释放后),会抛IllegalMonitorStateException。必须先判断isHeldByCurrentThread:
finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
坑点2:看门狗续期与JVM Stop-The-World
如果JVM发生长时间GC暂停,看门狗线程可能无法执行续期,导致锁超时自动释放。其他线程获取锁后,原线程恢复执行时出现并发问题。解决方案:对关键业务设置明确的leaseTime略大于业务最大执行时间,并捕获unlock时的异常做幂等处理。
坑点3:Redis主从切换导致锁失效
Redis主节点加锁成功后异步同步到从节点。如果主节点宕机,从节点提升为主时尚未收到锁数据,其他客户端可以重复加锁。Redisson提供RedLock算法,向多个独立Redis节点同时加锁,多数成功即认为加锁成功:
RLock lock1 = redissonClient.getLock("lock_key");
RLock lock2 = redissonClient2.getLock("lock_key");
RLock lock3 = redissonClient3.getLock("lock_key");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock(30, TimeUnit.SECONDS);
try {
// 业务逻辑
} finally {
redLock.unlock();
}
RedLock的争议在于增加了延迟和复杂度。对于金融级强一致性要求场景,建议使用Zookeeper或etcd实现的分布式锁。服务治理中根据业务场景选择合适的一致性级别,而非一味追求Redis方案。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-ji-cheng-redis-shi-xian-fen-bu-shi-suo/