Redisson分布式锁防止重复初始化问题
在分布式系统开发中,多节点部署的服务经常会遇到重复初始化的问题。例如在服务启动阶段,多个实例可能同时执行初始化任务,导致资源重复加载、数据重复处理或者配置被多次覆盖。引入Redisson分布式锁可以有效解决这类问题,保证同一时间只有一个节点能执行初始化逻辑。
重复初始化的常见场景
哪些场景容易出现重复初始化问题呢?下面列举几个典型情况:
服务集群启动时,多个实例同时执行定时任务触发的初始化逻辑
分布式环境下,多个节点同时尝试加载本地缓存、初始化数据库连接池等资源
业务流程中需要初始化全局配置,多个请求同时触发初始化操作
Redisson分布式锁的核心原理
Redisson是基于Redis实现的Java驻内存数据网格,它提供的分布式锁基于Redis的SET key value NX PX命令实现,具备可重入、自动续期、防止死锁等特性。在使用分布式锁防止重复初始化时,核心思路是:在执行初始化逻辑前尝试获取锁,获取成功则执行初始化,执行完成后释放锁;获取失败则说明已经有其他节点在执行初始化,当前节点可以直接跳过或者等待初始化完成。
基于Redisson实现防重复初始化的代码示例
下面以Spring Boot项目为例,展示如何使用Redisson分布式锁避免重复初始化:
1. 引入Redisson依赖
在项目的pom.xml中添加Redisson的starter依赖:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.8</version> </dependency>
2. 配置Redisson客户端
在application.yml中添加Redis连接配置:
redisson: config: | singleServerConfig: address: "redis://127.0.0.1:6379" password: null database: 0 connectionPoolSize: 64 connectionMinimumIdleSize: 10 idleConnectionTimeout: 10000 connectTimeout: 10000 timeout: 3000
3. 实现防重复初始化的逻辑
编写一个初始化服务类,使用Redisson分布式锁控制初始化逻辑的执行:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class InitService {
@Autowired
private RedissonClient redissonClient;
// 初始化锁的key,最好使用和业务相关的唯一标识
private static final String INIT_LOCK_KEY = "service:init:global_lock";
/**
* 执行初始化操作,使用分布式锁防止重复执行
*/
public void doInit() {
// 获取分布式锁实例
RLock lock = redissonClient.getLock(INIT_LOCK_KEY);
boolean lockAcquired = false;
try {
// 尝试获取锁,等待时间3秒,锁过期时间30秒,避免死锁
lockAcquired = lock.tryLock(3, 30, TimeUnit.SECONDS);
if (lockAcquired) {
// 再次检查是否已经初始化,避免锁过期后重复执行
if (isAlreadyInitialized()) {
System.out.println("初始化已完成,跳过执行");
return;
}
// 执行实际的初始化逻辑
System.out.println("开始执行初始化操作...");
// 模拟初始化耗时
Thread.sleep(2000);
// 标记初始化完成
markInitialized();
System.out.println("初始化操作执行完成");
} else {
System.out.println("未获取到初始化锁,跳过执行");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("初始化线程被中断");
} finally {
// 只有获取到锁的线程才释放锁,避免释放其他线程的锁
if (lockAcquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 检查是否已经初始化,实际场景中可以从Redis、数据库或者本地缓存判断
*/
private boolean isAlreadyInitialized() {
// 这里模拟判断逻辑,实际可以根据业务实现
return false;
}
/**
* 标记初始化完成,实际场景中可以将状态写入Redis或数据库
*/
private void markInitialized() {
// 这里模拟标记逻辑,实际可以根据业务实现
}
}4. 在启动阶段触发初始化
可以通过实现Spring的CommandLineRunner接口,在服务启动后触发初始化:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class InitRunner implements CommandLineRunner {
@Autowired
private InitService initService;
@Override
public void run(String... args) {
initService.doInit();
}
}注意事项
使用Redisson分布式锁防止重复初始化时,需要注意以下几点:
锁的key要保证全局唯一,避免和其他业务的锁冲突,建议使用业务前缀+功能标识的命名方式
要设置合理的锁过期时间,既要避免过期时间太短导致初始化未完成锁就过期,也要避免过期时间太长导致出现问题时锁长时间不释放
获取锁成功之后,最好再次检查初始化状态,防止因为锁过期、网络波动等特殊情况导致重复执行
释放锁的时候要判断当前线程是否持有锁,避免释放其他线程已经获取的锁,导致锁失效
如果初始化逻辑执行失败,要根据业务场景决定是否删除初始化状态标记,避免下次启动时直接跳过错误的初始化
总结
Redisson分布式锁是解决分布式环境下重复初始化问题的有效方案,通过Redis的原子性操作和Redisson提供的锁特性,可以很好地保证同一时间只有一个节点执行初始化逻辑。在实际使用中,结合业务场景合理设计锁的key、过期时间以及状态校验逻辑,就能稳定地避免重复初始化带来的各种问题。