schedule-joblisted
Install: claude install-skill 1398281322-a11y/java-backend-guardrails
# 定时任务 / 调度
## When to Invoke
日切对账、账期结算批、补偿扫描、报表、面试「@Scheduled 集群会怎样」「Timer 和线程池调度区别」「XXL-JOB 分片」。
## When NOT
下单 N 分钟关单、券过期这类 **一次性延迟** → `delay-job`(发延迟消息,禁止扫全表)。立刻异步解耦 → `mq-reliability`。任务线程池参数细节 → `thread-pool`。
## 风险(面试考点)
`java.util.Timer`:**单线程**;一个任务卡住后面全延迟;未捕获异常 **杀死 Timer 线程**,后续任务全停;基于绝对时钟,改系统时间会乱。JDK 文档写明用 `ScheduledThreadPoolExecutor` 替代。
Spring `@Scheduled` 默认 **线程池 size=1**(`spring.task.scheduling.pool.size`,Boot 2.1+)。一个慢 Job 堵住所有 cron。同一方法在单线程上不会重叠;**多实例每人一份调度器,任务会跑 N 遍**——这不是 Bug。
ShedLock / Redis 锁只能保证「同时最多一个」;`lockAtMostFor` 到期会把锁交给别人,长任务必须大于最坏耗时或可续期。锁不是幂等:任务仍要按业务键可重入。
XXL-JOB(官方能力,别背错):
- **调度过期**:忽略(推荐)/ 立即补偿一次。补偿可能双跑,业务必须幂等。
- **阻塞处理**:单机串行(默认排队);丢弃后续;覆盖之前(会打断正在跑的)。
- **分片广播**:所有执行器都触发,用 `shardIndex/shardTotal` 切数据;不是「只跑一台」。
- 超时会中断任务,循环里要响应中断。失败重试次数 >0 时当重复执行。
Cron 时区写死 `Asia/Shanghai`。Spring 是 6 域(含秒)。`fixedRate` 按开始时间对齐,跑超周期会积压;`fixedDelay` ��上一次 **结束** 再等。
禁止用定时任务当关单主路径(见 `delay-job`)。补偿扫描只捞小窗口、带索引、LIMIT 批次。
## 方案选型(轻量优先)
| 场景 | 默认 |
|------|------|
| 单实例、几个轻量 Job | `@Scheduled` + 调大 scheduling pool |
| 多实例、Job 少 | `@Scheduled` + ShedLock(或 Redisson 锁) |
| 多实例、要控制台/分片/告警 | XXL-JOB |
| 一次性延迟 | 不走本文件 |
```java
@Scheduled(cron = "0 0 2 * * ?", zone = "Asia/Shanghai")
@SchedulerLock(name = "settleDaily", lockAtMostFor = "50m", lockAtLeastFor = "1m")
public void settle() {
// 批次 LIMIT;幂等 uk;失败可重跑
}
```
XXL-JOB 大批量:
```java
@XxlJob("verifyCompensate")
public void compensate() {
int idx = XxlJobHelper.getShardIndex();
int tota