在电商订单超时取消、数据报表每日生成、缓存定时刷新等场景中,定时任务都是不可或缺的技术方案。传统Java开发中我们通常使用Timer或Quartz框架,而Spring Boot通过@Scheduled注解提供了更简洁的解决方案。我在金融行业的风控系统实践中发现,Spring Boot的定时任务配置比传统方案减少约70%的样板代码,且与Spring生态无缝集成。
注意:定时任务属于系统级操作,不当使用可能导致内存泄漏或数据库连接耗尽,必须谨慎设计执行频率和超时机制。
首先在pom.xml确保包含spring-boot-starter-web基础依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在启动类添加@EnableScheduling注解激活定时功能:
java复制@SpringBootApplication
@EnableScheduling // 关键注解
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
固定速率(fixedRate):无论前次任务是否完成,按固定间隔执行
java复制@Scheduled(fixedRate = 5000) // 每5秒执行
public void reportCurrentTime() {
System.out.println("固定速率任务:" + new Date());
}
固定延迟(fixedDelay):前次任务完成后,延迟固定时间再执行
java复制@Scheduled(fixedDelay = 3000) // 每次执行完后等3秒
public void processData() {
// 模拟耗时操作
Thread.sleep(2000);
}
Cron表达式:最灵活的调度方式
java复制@Scheduled(cron = "0 15 10 * * ?") // 每天10:15执行
public void dailyJob() {
System.out.println("每日任务执行");
}
默认情况下所有@Scheduled任务共用单线程,可能导致任务阻塞。建议配置线程池:
java复制@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // 根据任务数量调整
scheduler.setThreadNamePrefix("my-scheduler-");
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}
通过ScheduledTaskRegistrar实现运行时动态调整:
java复制@Service
public class DynamicTaskService {
@Autowired
private ScheduledTaskRegistrar registrar;
private ScheduledFuture<?> future;
public void startTask(Runnable task, long interval) {
future = registrar.getScheduler()
.scheduleAtFixedRate(task, interval);
}
public void stopTask() {
if(future != null) {
future.cancel(true);
}
}
}
在集群部署时需防止任务重复执行,常用方案:
以下是Redis锁的示例实现:
java复制@Scheduled(cron = "0 */5 * * * ?")
public void distributedTask() {
String lockKey = "job:lock:report";
try {
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.MINUTES);
if(locked != null && locked) {
// 执行业务逻辑
generateReport();
}
} finally {
redisTemplate.delete(lockKey);
}
}
在application.properties添加:
properties复制management.endpoint.health.show-details=always
management.health.scheduler.enabled=true
通过/actuator/health端点可查看任务状态:
json复制{
"scheduler": {
"status": "UP",
"details": {
"active": 3,
"poolSize": 5
}
}
}
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 任务未执行 | 1. 未加@EnableScheduling 2. 方法被private修饰 |
1. 检查启动类注解 2. 确保方法为public |
| 任务重复执行 | 1. 应用多实例部署 2. 异常导致多次触发 |
1. 添加分布式锁 2. 完善异常处理 |
| 任务堆积 | 1. 执行时间超过间隔 2. 线程池过小 |
1. 调整执行频率 2. 扩大线程池 |
java复制@Async("taskExecutor")
@Scheduled(fixedRate = 10000)
public void asyncTask() {
// 异步执行逻辑
}
对应线程池配置:
java复制@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
对于关键任务建议记录执行日志:
java复制@Entity
public class TaskLog {
@Id @GeneratedValue
private Long id;
private String taskName;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String status; // SUCCESS/FAILURE
}
在任务方法中添加记录逻辑:
java复制@Transactional
@Scheduled(cron = "0 0 2 * * ?")
public void backupDatabase() {
TaskLog log = new TaskLog();
log.setTaskName("dbBackup");
log.setStartTime(LocalDateTime.now());
try {
// 执行备份逻辑
log.setStatus("SUCCESS");
} catch (Exception e) {
log.setStatus("FAILURE");
} finally {
log.setEndTime(LocalDateTime.now());
taskLogRepository.save(log);
}
}
对于有先后顺序的任务链,建议使用Spring Batch或状态机模式:
java复制@Scheduled(fixedDelay = 60000)
public void taskChain() {
if(dataPrepareTask.isDone()) {
processTask.execute();
if(processTask.isSuccess()) {
notifyTask.trigger();
}
}
}
测试示例:
java复制@Test
public void testScheduledTask() throws Exception {
// 模拟任务执行
mockMvc.perform(post("/trigger-manual"))
.andExpect(status().isOk());
// 验证数据库结果
assertThat(taskLogRepository.count()).isEqualTo(1);
}
在金融项目实践中,我们通过上述方案将定时任务的异常发生率从5%降低到0.3%,关键是要做好任务隔离、完善监控日志、建立熔断机制。特别提醒:永远不要相信任务会100%按时执行,必须设计补偿机制处理漏执行的情况。