Spring 定时任务的执行原理
文章目录
前言
经典的 Spring 定时任务知识点!
📋 核心原因:Spring 的 @Scheduled 注解!
这个方法没有显式调用,但它会自动执行!因为加了 @Scheduled 注解!
第一步:Spring Boot 扫描并解析
当 Spring 项目启动时,会自动扫描带 @Scheduled 注解的方法:
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
public void refreshLocalCache() {
// 这个方法会被 Spring 定时任务调度器自动调用!
}
第二步:Spring 内部的调度机制
Spring Boot 内部有一个任务调度器(TaskScheduler),会自动执行这些定时任务:
项目启动
↓
Spring Boot 自动配置(@EnableScheduling)
↓
扫描所有 @Scheduled 注解的方法
↓
注册到任务调度器(TaskScheduler)
↓
启动后,按规则自动执行!
📋 完整的触发流程
| 时间点 | 操作 | 说明 |
|---|---|---|
| 启动时 | @PostConstruct 执行 initLocalCache |
缓存初始化,打印第一次日志 |
| 60秒后 | 调度器自动调用 refreshLocalCache |
第一次定时任务执行 |
| 再60秒后 | 调度器再次调用 | 第二次执行 |
| 再60秒后 | … | 无限循环… |
🔍 代码佐证:为什么没有显式调用?
在 MpAccountServiceImpl 里,refreshLocalCache 方法:
// ✅ 只加了注解,没有其他方法调用它!
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
public void refreshLocalCache() {
// ...
}
💡 Spring Boot 如何启用定时任务?
通常在启动类里,或者配置里,加了 @EnableScheduling 注解:
@EnableScheduling // ← 这个注解启用定时任务
@SpringBootApplication
public class YudaoServerApplication {
public static void main(String[] args) {
SpringApplication.run(YudaoServerApplication.class, args);
}
}
📊 总结对比
| 调用方式 | 例子 | 说明 |
|---|---|---|
| 显式调用 | service.method() |
你主动在代码里写调用语句 |
| 注解驱动调用 | @Scheduled |
Spring 容器自动调度,你不用管! |
🎯 你看到的日志来源
2026-04-25 18:27:45 → refreshLocalCache 执行
2026-04-25 18:28:45 → 60秒后,又自动执行!
2026-04-25 18:29:45 → 再60秒...
2026-04-25 18:30:45 → ...
这些日志都来自Spring 定时任务调度器,自动调用 refreshLocalCache → 然后 refreshLocalCache 调用 initLocalCache → 打印日志!
Spring 官方的定时任务机制,不需要显式调用!😎
📋 这个类是用来清理 Redis Stream 消息的!
这个定时任务,每小时执行一次,清理 Redis Stream 里的旧消息!
🎯 核心功能:清理 Redis Stream 消息
Redis Stream 是什么?
Redis Stream 是 Redis 5.0 新的数据结构,用来实现消息队列(MQ),这个项目用它做异步消息!
📊 定时任务的配置(第40行)
@Scheduled(cron = "0 0 * * * ?")
public void cleanup() {
// cron = "0 0 * * * ?" → 每小时执行一次!
// 0秒 0分 *时 → 每小时的整点执行
}
💡 完整的执行逻辑
第一步:加分布式锁(第42-51行)
RLock lock = redissonClient.getLock(LOCK_KEY);
// 尝试加锁
if (lock.tryLock()) {
// 加锁成功 → 执行清理
try {
execute();
} finally {
lock.unlock();
}
}
为什么加锁?
- 如果集群部署多节点!
- 防止多个节点同时清理!
- Redisson 分布式锁保证只有一个节点执行清理!
第二步:执行清理(第58-71行)
private void execute() {
StreamOperations<String, Object, Object> ops = redisTemplate.opsForStream();
listeners.forEach(listener -> {
try {
// 使用 XTRIM 命令!
Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, true);
if (trimCount > 0) {
log.info("[清理了 {} 条消息", trimCount);
}
} catch (Exception ex) {
log.error("[清理异常]", ex);
}
});
}
📊 工作原理图解
┌─────────────────────────────────────────────────────────┐
│ Redis Stream (消息队列) │
├─────────────────────────────────────────────────────────┤
│ 消息1 (老的,很久以前) │
│ 消息2 (老的) │
│ ... │
│ 消息9999 (最近的) │
│ 消息10000 (最近的) │
└─────────────────────────────────────────────────────────┘
↓ 每小时自动运行一次定时任务
使用 XTRIM streamKey MAXLEN ~ 10000
↓
┌─────────────────────────────────────────────────────────┐
│ 只保留最近10000条消息! │
│ 删除超过的老消息删掉,释放内存! │
└─────────────────────────────────────────────────────────┘
💡 为什么需要这个定时任务?
看注释(第18行):
/**
* 记一次 redis stream 数据类型内存不释放问题
*/
问题: Redis Stream 消息消费后不会自动删除,越积越多,内存会炸!
解决: 定时清理,只保留最近10000条!
📋 两个定时任务对比
| 定时任务类 | 频率 | 功能 |
|---|---|---|
MpAccountServiceImpl.refreshLocalCache() |
每分钟 | 刷新公众号账号缓存 |
RedisStreamMessageCleanupJob.cleanup() |
每小时 | 清理 Redis Stream 旧消息 |
🎯 总结
| 实现方式 | 注解 | 用途 |
|---------|------|
| 定时任务1 | @Scheduled(initialDelay=60, fixedRate=60) | 每分钟刷新缓存 |
| 定时任务2 | @Scheduled(cron="0 0 * * * ?") | 每小时清理Redis |
这都是 Spring 标准的定时任务,@Scheduled 注解驱动,Spring 容器自动调度执行!
更多推荐


所有评论(0)