目录
Redis 实现分布式锁及续约机制详解
- 在分布式系统中,多个节点对同一资源的访问可能会产生竞争,为了解决这个问题,我们需要一种机制来保证同一时间内只有一个节点可以访问资源,分布式锁便是其中的一种解决方案。Redis 作为一种高性能的分布式缓存系统,其自带的
SETNX
命令和过期时间机制非常适合用来实现分布式锁。本文将详细介绍如何利用 Redis 实现分布式锁,并引入一个守护线程为锁自动续约,确保在业务长时间执行的过程中锁不会被意外释放。
1、Redis 分布式锁的基本实现
- 第一版
- 在这个简单的实现中,我们通过
setIfAbsent
方法(对应 Redis 的SETNX
命令)来尝试获取锁。如果获取成功,就执行相应的业务逻辑,处理完后再检查当前锁是否仍属于自己(通过比对lockId
),如果是,则删除锁以释放资源。
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void RedisDistributedLock2() {
int i = 1;
// 线程获得唯一标识的锁ID
String lockId = UUID.randomUUID().toString();
// 使用 Redis 的 setNx 命令,同时设置过期时间,保证原子性
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("key", lockId, 15, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lock)) {
try {
i += 10;
// 处理业务逻辑
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
if (lockId.equals(stringRedisTemplate.opsForValue().get("key"))) {
stringRedisTemplate.delete("key");
}else{
System.out.println("当前线程持有锁已过期!");
}
}
}else {
// 获取锁失败,快速返回
System.out.println("获取锁失败,当前线程无法执行任务。");
return; // 直接返回,终止方法执行
}
}
- 虽然上述代码能够实现分布式锁的基本功能,但在某些场景下,锁的过期时间可能会过短,导致在业务逻辑尚未执行完毕时锁被自动释放,从而可能导致其他线程错误地获取到锁。为了解决这个问题,我们需要引入一种机制来定期延长锁的过期时间。
2、引入守护线程[看门狗机制】
- 为了保证在业务逻辑长时间执行时锁不会过期,我们可以通过守护线程来为锁续约。下面是具体的实现
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final int LOCK_EXPIRE_TIME = 15; // 锁的过期时间,单位为秒
private static final int RENEWAL_INTERVAL = 10; // 续约的间隔时间,单位为秒
@Test
void RedisDistributedLock(){
int i=1;
//某线程获取到锁唯一标识
String lockId = UUID.randomUUID().toString();
//使用Redis的setNx命令,同时过期时间一起,保证原子性
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("key", lockId, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lock)){
// 创建续约标志,确保可以终止续约线程
AtomicBoolean continueRenewal = new AtomicBoolean(true);
// 守护线程进行锁续约
Thread renewalThread = new Thread(() -> {
try {
while (continueRenewal.get()) {
Thread.sleep(TimeUnit.SECONDS.toMillis(RENEWAL_INTERVAL));
// 如果锁ID匹配,则续约
if (lockId.equals(stringRedisTemplate.opsForValue().get("key"))) {
stringRedisTemplate.expire("key", LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
} else {
break; // 锁已不再属于当前线程,退出续约线程
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
renewalThread.setDaemon(true); // 设置为守护线程
renewalThread.start();
try{
i+=10;
}catch (Exception e){
e.printStackTrace();
}finally {
//如果是当前线程的锁,操作完成释放锁
if (lockId.equals(stringRedisTemplate.opsForValue().get("key"))){
continueRenewal.set(false); // 停止续约线程
stringRedisTemplate.delete("key");
}else{
System.out.println("当前线程持有锁已过期!");
}
}
}else {
// 获取锁失败,快速返回
System.out.println("获取锁失败,当前线程无法执行任务。");
return; // 直接返回,终止方法执行
}
}
- 续约线程的引入:在成功获取锁后,我们创建并启动一个守护线程,该线程会每隔一段时间检查当前锁的状态,如果锁仍属于当前线程,则通过
expire
方法延长锁的过期时间。 - 续约逻辑:续约线程的执行周期由
RENEWAL_INTERVAL
控制,每次延长的过期时间为LOCK_EXPIRE_TIME
。这样即使业务逻辑执行时间较长,锁也不会因为过期而被释放。 - 守护线程的终止:在释放锁之前,我们首先将
continueRenewal
置为false
,使守护线程停止续约操作,然后再删除锁。这种设计避免了锁被释放后续约线程仍在运行的风险。
总结:以上便是 Redis 实现分布式锁及其续约机制的详细介绍,希望对你有所帮助。如果你在实际开发中遇到问题或有其他更好的方案,欢迎交流讨论。
标签:续约,实现,Redis,过期,线程,stringRedisTemplate,分布式 From: https://blog.csdn.net/m0_64022678/article/details/141159485