第 60 章:定时任务
学习目标
- 用 Spring Schedule 做简单定时
- 用 Xxl-Job 做分布式定时
- 避免重复执行
一、Spring Schedule(单机)
java
@SpringBootApplication
@EnableScheduling
public class App { }
@Component
public class MyTask {
@Scheduled(fixedRate = 5000) // 每 5 秒
public void report() {
log.info("统计: {}", LocalDateTime.now());
}
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点
public void clean() {
log.info("清理过期数据");
}
}cron 表达式(6/7 位):
秒 分 时 日 月 周 [年]
0 0 2 * * ? 每天 2 点
0 0 9 * * MON 每周一 9 点(注意周位置用 ?)
*/10 * * * * * 每 10 秒工具:cron 在线生成
⚠️ 坑 1:多机部署时 Spring Schedule 会重复执行。N 台机器 = N 次执行。
二、Xxl-Job(分布式)
架构:调度中心 + 执行器。
[Admin 调度中心] -- 调度 --> [Executor 执行器]
↓
你的应用2.1 部署 Admin
bash
docker run -d --name xxl-job-admin -p 8080:8080 \
-e PARAMS="--spring.datasource.url=jdbc:mysql://...xxl_job..." \
xuxueli/xxl-job-admin:2.4.02.2 应用集成
xml
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>yaml
xxl:
job:
admin:
addresses: http://localhost:8080/xxl-job-admin
executor:
appname: my-app
port: 9999
logpath: /data/applogs/xxl-jobjava
@Configuration
public class XxlJobConfig {
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
executor.setAdminAddresses("http://localhost:8080/xxl-job-admin");
executor.setAppname("my-app");
executor.setPort(9999);
executor.setLogPath("/data/applogs/xxl-job");
return executor;
}
}2.3 写任务
java
@Component
public class MyJobHandler {
@XxlJob("myJob")
public void execute() {
XxlJobHelper.log("开始执行");
try {
// 业务逻辑
XxlJobHelper.handleSuccess();
} catch (Exception e) {
XxlJobHelper.handleFail("失败: " + e.getMessage());
}
}
}2.4 控制台配置
- 进入 Admin:http://localhost:8080/xxl-job-admin
- 任务管理 → 新增 → 填
JobHandler=myJob、Cron、路由策略 - 启动,执行器自动注册
三、路由策略(解决多机执行)
| 策略 | 行为 |
|---|---|
| 第一个 | 只在第一台机器执行 |
| 最后一个 | 只在最后一台执行 |
| 轮询 | 轮询分到不同机器 |
| 分片广播 | 所有机器都执行,各自处理一部分数据 |
| 一致性哈希 | 按 ID 哈希分 |
java
@XxlJob("shardingJob")
public void shardingJob() {
int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片
int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数
// 只处理 index % shardTotal == shardIndex 的数据
List<Long> ids = ...
for (Long id : ids) {
if (id % shardTotal == shardIndex) {
process(id);
}
}
}四、阻塞处理策略
| 策略 | 行为 |
|---|---|
| 单机串行 | 排队,等上一个执行完 |
| 丢弃后续 | 取消当前 |
| 覆盖之前 | 杀旧的,起新的 |
⚠️ 坑 2:避免并发问题,选「单机串行」。
五、为什么不用 @Scheduled 后接 RequestMapping
java
// ❌ 定时任务调 HTTP 接口
@Scheduled(fixedRate = 5000)
public void sync() {
restTemplate.getForEntity("http://localhost:8080/api/sync");
}为什么错?需要 HTTP,慢、超时、失败无法控制。
直接调 Service:
java
@XxlJob("sync")
public void sync() {
syncService.execute(); // 直接调本地方法
}六、本章小结
| 要点 | 关键 |
|---|---|
| Spring Schedule | 单机,但多机会重复 |
| Xxl-Job | 分布式,Admin 控制 |
| 分片 | 处理大数据量 |
| 阻塞 | 单机串行最稳 |
动手练习
- 部署 Xxl-Job Admin(docker)
- 写一个定时任务,每 10 秒打印时间
下一章:第 61 章:文件上传与 WebSocket →