✅ 一、常见定时器类型
| 定时器类型 | 特点 | 风险点 |
|---|---|---|
@Scheduled(Spring) |
简单、固定间隔 | 单线程、阻塞整个调度器 |
| ScheduledExecutorService | 可配置线程池 | 线程池耗尽 |
| XXL-JOB、ElasticJob | 分布式调度 | 调度过频/单机压力大 |
自写 Timer |
单线程 | 异常导致整个 Timer 线程退出 |
许多崩溃问题和这些机制有关。
🚨 二、系统崩溃的常见根因(按频率排序)
1. 定时任务阻塞/卡死(最常见)
❌ 场景举例:
你用 @Scheduled(fixedRate=5000),任务执行需要 30 秒。
问题:
- Spring 的
ScheduledAnnotationBeanPostProcessor默认是 单线程执行器 - 上一次任务还没结束,下一次又来
- 排队越来越长
- CPU 占用飙升
- 最终线程卡死或 OOM
less
@EnableScheduling
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
registrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
2. 定时任务抛异常导致线程退出
❌ 如果使用 Timer:
java
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
int a = 1 / 0; // 异常
}
}, 0, 1000);
结果:
- Timer 线程会完全退出
- 之后所有任务都不再执行
- 某些关键操作失效 → 系统异常甚至宕机
3. 任务执行时间过长导致资源积压
例如:
- 定时任务扫描数据库 50W 条记录
- 每分钟执行一次
- 查询/锁造成数据库压力过大
- 系统数据库连接耗尽
- 业务接口全挂
✔ 真实案例:
"定时对账任务"每 10 分钟执行,执行一次需要 12 分钟,后来数据库基本 100% CPU,业务查询都 timeout。
4. 任务中出现死循环 / 无限重试
ruby
while (true) {
// MQ 超时就 retry
}
结果:
- CPU 100%
- 内存不断增加
- 最终 OOM 直接导致系统崩溃
5. 定时任务并发执行无锁,导致 DB 互相死锁
ini
update table1 set status=1 where status=0;
任务 B 同时执行同一逻辑 → 死锁概率大增,导致:
- MySQL 大量锁等待
- 1205 锁等待超时
- 线程池阻塞
- 系统吞吐量骤降
- 最终接口超时、服务假死
6. 任务日志大量输出导致磁盘写满
arduino
[INFO] task running...
大量日志文件导致:
/var/log或容器磁盘写满- 应用无法写日志 → 报错
- MySQL 也可能无法写入 binlog → 崩溃
7. 定时任务里使用了阻塞 IO(HTTP、Redis、MQ 等)
如果第三方 API 超时,任务一直卡着,直接阻塞定时线程,导致:
- 定时任务全卡住
- 所有任务执行延迟
- 如果线程池只有 1 个,全部任务失效
8. 定时任务内存泄漏
特别是你常写类似:
ini
List<Object> cache = new ArrayList<>();
放在静态变量中,每次执行都 append,不清理。
长期运行 → 堆积 → OOM → 服务崩溃
🚩 三、常见"定时器导致系统崩溃"的真实场景总结
| 场景 | 后果 |
|---|---|
| 定时任务读取大量数据 | 数据库压力爆炸 |
| 多个任务同时扫描数据库 | 死锁 + 慢 SQL |
| 任务执行时间 > 调度间隔 | 任务排队,线程池耗尽 |
| 一个异常导致整个定时器退出 | 关键任务不再执行,系统雪崩 |
| 日志打印过多写满磁盘 | 服务无法写日志 → 崩溃 |
| 定时任务中线程未关闭 | 线程数爆增,系统挂掉 |