Spring Boot3 实现定时任务 每10分钟执行一次,同时要解决分布式的问题 区分不同场景
在Spring Boot 3中实现分布式定时任务,确保多实例环境下任务仅执行一次,可以采用以下方案:
方案一:Redis分布式锁(推荐)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.concurrent.TimeUnit;@Component
public class DistributedScheduler {private final StringRedisTemplate redisTemplate;private static final String LOCK_KEY = "TASK_LOCK:MY_TASK";private static final int LOCK_TIMEOUT = 9; // 锁超时时间(分钟)public DistributedScheduler(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Scheduled(cron = "0 */10 * * * *")public void scheduledTask() {Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "locked", Duration.ofMinutes(LOCK_TIMEOUT));if (lockAcquired != null && lockAcquired) {try {// 执行任务逻辑performTask();} finally {// 任务完成后手动释放锁(可选)// redisTemplate.delete(LOCK_KEY);}}}private void performTask() {// 具体任务代码System.out.println("Task executed at: " + new Date());}
}
依赖配置:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
关键点:
- 使用
setIfAbsent
原子性操作获取锁,避免并发问题。 - 设置锁的自动过期时间(略小于任务间隔),防止死锁。
- 根据业务需求选择是否手动释放锁(如任务执行时间可能超过锁超时时间)。
方案二:数据库乐观锁
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;@Component
public class DatabaseLockScheduler {@Scheduled(cron = "0 */10 * * * *")@Transactionalpublic void scheduledTask() {// 1. 查询最近一次任务记录TaskLock lastLock = taskLockRepository.findTopByTaskNameOrderByExecuteTimeDesc("MY_TASK");// 2. 检查是否已执行过if (lastLock != null && lastLock.getExecuteTime().isAfter(LocalDateTime.now().minusMinutes(10))) {return;}// 3. 插入新记录(利用唯一约束或版本号控制并发)TaskLock newLock = new TaskLock("MY_TASK", LocalDateTime.now());taskLockRepository.save(newLock);// 执行任务逻辑performTask();}
}
实体类示例:
@Entity
public class TaskLock {@Idprivate String taskName;private LocalDateTime executeTime;@Versionprivate Integer version;// 省略构造方法/getter/setter
}
关键点:
- 使用数据库唯一约束(复合唯一索引)或版本号控制并发。
- 需要处理可能的异常(如唯一约束冲突)。
方案三:Quartz集群模式
配置步骤:
- 添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 配置数据库存储(
application.properties
):
spring.quartz.job-store-type=jdbc
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000
- 定义任务:
public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext context) {// 任务逻辑}
}
- 配置调度器:
@Configuration
public class QuartzConfig {@Beanpublic JobDetail jobDetail() {return JobBuilder.newJob(MyJob.class).withIdentity("myTask").storeDurably().build();}@Beanpublic Trigger trigger() {return TriggerBuilder.newTrigger().forJob(jobDetail()).withSchedule(CronScheduleBuilder.cronSchedule("0 */10 * * * ?")).build();}
}
方案对比
方案 | 优点 | 缺点 |
---|---|---|
Redis锁 | 实现简单,性能高 | 依赖Redis,需处理锁续期问题 |
数据库锁 | 无需额外中间件 | 数据库压力大,需处理并发冲突 |
Quartz集群 | 官方集群支持,功能强大 | 配置复杂,依赖数据库表结构 |
选择建议:
- 轻量级场景优先使用Redis锁
- 已有数据库基础设施可考虑数据库锁
- 复杂调度需求选择Quartz集群