rscheduledexecutorservice是spring boot中实现真正分布式定时任务最轻量可靠的方式,所有调度元数据与执行状态均落盘redis,天然支持多节点协调与故障转移。

Redisson 的 RScheduledExecutorService 是目前 Spring Boot 项目中实现「真正分布式」定时/周期任务最轻量且可靠的方式——它不依赖本地线程池,所有调度元数据、执行状态、失败重试都落盘到 Redis,天然支持多节点协调与故障转移。
为什么不能直接用 Spring @Scheduled 或 ScheduledExecutorService
本地调度器(如 @Scheduled、ScheduledExecutorService)在单机上运行良好,但部署多个实例时会重复触发任务,导致订单超时取消执行两次、库存扣减翻倍等严重问题。即使加了数据库锁或 Redis 分布式锁,也无法解决「调度时间点本身不一致」的问题:每个节点的 JVM 独立计算下一次执行时间,无法对齐。
而 RScheduledExecutorService 把「什么时候该执行」这个决策权完全交给 Redis,所有节点共享同一个 Sorted Set(按执行时间戳排序的任务队列),天然避免重复调度。
常见错误现象包括:
- 定时任务在集群中被多个节点同时执行
- 节点重启后,未完成的延迟任务丢失(本地内存态)
- Cron 表达式在不同节点因时区或系统时间偏差导致错位执行
必须配置 redisson-spring-boot-starter 3.20.0+ 版本
低版本(如 3.16.x)的 RScheduledExecutorService 存在严重缺陷:任务提交后若节点宕机,任务不会自动迁移;且不支持 CronSchedule.of() 的完整表达式语法(比如 "0 */5 * * * ?" 中的 */5 可能被忽略)。
Spring Boot 3.x 项目应使用以下依赖:
<dependency><groupid>org.redisson</groupid><artifactid>redisson-spring-boot-starter</artifactid><version>3.20.0</version></dependency>
YAML 配置中务必启用 executor 模块(默认关闭):
spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
# 必须显式开启 executor 支持
threads: 16
nettyThreads: 32
注意:threads 和 nettyThreads 不是可选参数——它们控制任务执行线程池大小,设为 0 会导致 schedule() 调用静默失败,无任何日志或异常。
提交任务时必须传入可序列化的 Runnable / Callable
Redisson 会把任务对象序列化后存入 Redis,因此不能直接传入 Lambda 表达式(如 () -> System.out.println("ok")),也不能引用 Spring Bean(如 this.orderService.cancel(...)),否则反序列化时抛 ClassNotFoundException 或 NotSerializableException。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
正确做法是定义独立的、无状态的类:
public class OrderTimeoutTask implements Runnable, Serializable {
private static final long serialVersionUID = 1L;
private final Long orderId;
<pre class="brush:php;toolbar:false;">public OrderTimeoutTask(Long orderId) {
this.orderId = orderId;
}
@Override
public void run() {
// 注意:此处无法直接注入 Spring Bean
// 需通过 ApplicationContextHolder 或手动获取
OrderService service = ApplicationContextProvider.getBean(OrderService.class);
service.cancelTimeoutOrder(orderId);
}}
关键要点:
- 类必须实现
Serializable,并声明serialVersionUID - 构造参数只能是基础类型、String、或其它可序列化对象(禁止传入
HttpServletRequest、Connection等) - 业务逻辑中若需 Spring Bean,推荐使用静态工具类
ApplicationContextProvider获取,而非依赖注入 - 不要在任务中操作本地文件、Socket 连接或非线程安全的静态变量
scheduleAtFixedRate 与 CronSchedule 的行为差异
scheduleAtFixedRate 和 scheduleWithFixedDelay 是固定周期模型,适用于「每 N 秒检查一次状态」这类场景;而 CronSchedule.of() 是时间点驱动,适用于「每天 2 点执行报表生成」这类严格时间要求的场景。
二者在 Redisson 中底层实现不同:
-
scheduleAtFixedRate:首次执行后,后续每次都在「上一次开始时间 + period」触发,不考虑执行耗时。若任务执行超时,可能堆积 -
CronSchedule.of("0 0 2 * * ?"):严格按 cron 解析出的下一个合法时间点触发,即使前一次执行延迟,也不会补漏,但绝不会提前
示例:
RScheduledExecutorService executor = redissonClient.getSchedulerExecutorService("report");
// 每天凌晨 2 点执行
executor.schedule(() -> reportService.generate(), CronSchedule.of("0 0 2 * * ?"));
<p>// 每 30 秒检查一次库存水位(固定间隔)
executor.scheduleAtFixedRate(
() -> inventoryService.checkLowStock(),
0, 30, TimeUnit.SECONDS
);</p>
容易踩的坑:
- 误用
scheduleAtFixedRate替代 cron——比如想「每天 9 点发通知」,却写成每 24 小时执行,结果第一次执行是 9:05,之后就永远错位 - 在 cron 表达式中混用秒级和毫秒级单位(Redisson cron 不支持毫秒,只识别到秒)
- 未设置任务最大重试次数,默认失败后无限重试,可能压垮 Redis 或下游服务
最常被忽略的一点:Redisson 的调度任务默认不记录执行结果,也不提供失败告警。如果业务要求「必须成功」,你需要自己封装一层:监听 RScheduledFuture 的 get() 结果,捕获 ExecutionException,再写入告警队列或重试表。否则,一个网络抖动就可能导致关键任务静默失败。










