任务调度
Laravel 用
routes/console.php+ Kernel 的schedule()定义计划任务,配 cron 驱动;Spring Boot 用@Scheduled注解,一个方法就是一个定时任务,无需额外进程。
三步启用
1. 开启调度
java
@Configuration
@EnableScheduling
public class SchedulingConfig { }2. 写定时任务
java
@Component
public class ReportJob {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点
public void generateDailyReport() {
System.out.println("生成日报表...");
}
}3. 就完了
无需 php artisan schedule:work,Spring Boot 应用启动后自带调度器线程。
对照 Laravel
| Laravel | Spring Boot |
|---|---|
Kernel schedule() + cron 触发 | @Scheduled 注解 |
php artisan schedule:run(每分钟 cron 触发) | 应用内调度线程,无需外部 cron |
->dailyAt('02:00') | @Scheduled(cron = "0 0 2 * * ?") |
->everyFiveMinutes() | @Scheduled(fixedRate = 300000) |
cron 表达式的坑:Java 六段式
Laravel 用五段式 cron,Spring 用六段式(多了「秒」),开头结尾容易写错:
秒 分 时 日 月 周
0 0 2 * * ?| 段 | 含义 |
|---|---|
| 1 | 秒(Spring 独有!Laravel 没有) |
| 2 | 分 |
| 3 | 时 |
| 4 | 日 |
| 5 | 月 |
| 6 | 周 |
两个最常踩的坑
- 多了一个「秒」:Laravel 的
0 2 * * *(凌晨 2 点)在 Spring 里要写成0 0 2 * * ? - 日和周互斥:两者同时填会冲突。Spring 用
?表示「不指定」:- 每天凌晨 2 点:
0 0 2 * * ? - 每周一凌晨 2 点:
0 0 2 ? * MON
- 每天凌晨 2 点:
常用调度写法
| 需求 | Spring 写法 |
|---|---|
| 每天 02:00 | @Scheduled(cron = "0 0 2 * * ?") |
| 每小时整点 | @Scheduled(cron = "0 0 * * * ?") |
| 每分钟 | @Scheduled(fixedRate = 60000) |
| 每 5 分钟 | @Scheduled(cron = "0 */5 * * * ?") |
| 启动后 3 秒执行一次,之后每 10 秒 | @Scheduled(initialDelay = 3000, fixedRate = 10000) |
| 每 10 秒(间隔从上一次完成算) | @Scheduled(fixedDelay = 10000) |
| 周一到周五 09:00 | @Scheduled(cron = "0 0 9 ? * MON-FRI") |
fixedRate vs fixedDelay
fixedRate:固定频率,到点就执行(上一个没跑完也可能并发触发)fixedDelay:上一个执行完后间隔再执行,天然串行 默认单线程,任务耗时长的选fixedDelay或配置线程池。
任务执行在哪个线程
默认单线程执行所有定时任务,一个任务卡住会阻塞其他任务。配置线程池:
yaml
spring:
task:
scheduling:
pool:
size: 5从数据库读取 cron(动态调度)
@Scheduled 的 cron 在编译期写死。要「运行时可改」的定时任务(比如后台配置),用 SchedulingConfigurer:
java
@Component
public class DynamicJob implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addCronTask(() -> System.out.println("动态任务执行"),
() -> "0 */30 * * * ?"); // 每次执行前查库拿最新 cron
}
}TIP
configureTasks 里的 cron 提供函数每次执行前都会调用,所以你可以在 lambda 里查数据库/配置中心拿到最新 cron。这是 Spring 应对「配置化定时任务」的标准姿势。
分布式部署:别重复执行
单实例没问题;多实例部署时,每个实例都会跑同一份 @Scheduled,任务会重复执行。
解决:引入分布式锁。轻量方案用 Redis 锁:
java
@Scheduled(cron = "0 0 2 * * ?")
public void dailyJob() {
// 用 Redis setnx 抢锁,抢到才执行(伪代码)
boolean locked = redisLock.tryLock("job:daily", Duration.ofMinutes(10));
if (!locked) return;
try {
generateReport();
} finally {
redisLock.unlock("job:daily");
}
}更省心的选择
引入 spring-boot-starter-quartz,用 Quartz 的集群模式(基于数据库锁)天然避免重复调度。但若项目只是简单任务,Redis 锁就够了,别为调度器引入重量级依赖。
对照速查
| 需求 | Laravel | Spring Boot |
|---|---|---|
| 定时任务 | Kernel schedule() | @Scheduled |
| cron 格式 | 五段式 | 六段式(多秒) |
| 指定时间 | ->dailyAt('02:00') | cron = "0 0 2 * * ?" |
| 固定间隔 | ->everyFiveMinutes() | fixedRate = 300000 |
| 启动延迟 | ->onOneServer() 等 | initialDelay |
| 单实例限制 | ->onOneServer() | Redis 锁 / Quartz 集群 |
| 动态 cron | 较麻烦 | SchedulingConfigurer |