1. 延时任务需求解析与方案选型
延时任务(Delayed Task)是业务系统中常见的功能需求,指在指定时间点或延迟指定时长后执行特定操作。在电商系统中常用于订单超时取消、在物流系统中用于配送状态更新、在金融领域用于定时结算等场景。Spring Boot 3环境下实现延时任务需要考虑线程模型、任务持久化、分布式协调等关键因素。
当前主流实现方案可分为四类:
- JDK原生方案:Timer + TimerTask组合或ScheduledExecutorService
- 消息队列方案:RabbitMQ死信队列或RocketMQ延迟消息
- 调度框架方案:Quartz或XXL-JOB等专业调度系统
- Redis方案:基于ZSET或Keyspace Notifications实现
方案选型需考虑三个维度:
- 精度要求:秒级精度推荐Redis或Quartz,分钟级可用消息队列
- 可靠性要求:高可靠场景需配合数据库持久化
- 集群环境:分布式场景需避免任务重复执行
关键提示:Spring Boot 3默认使用Spring Framework 6,其异步任务处理模块有重大改进,推荐优先考虑Spring原生方案组合
2. 基于Spring Scheduling的核心实现
2.1 基础定时任务配置
Spring Boot 3中启用定时任务只需在启动类添加@EnableScheduling注解:
java复制@SpringBootApplication
@EnableScheduling
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
实现固定延迟任务示例:
java复制@Component
public class PaymentTimeoutTask {
private static final Logger log = LoggerFactory.getLogger(PaymentTimeoutTask.class);
@Scheduled(fixedDelay = 5000) // 上次执行结束后5秒再次执行
public void checkUnpaidOrders() {
log.info("开始扫描超时未支付订单...");
// 查询超过30分钟未支付的订单
List<Order> orders = orderRepository.findByStatusAndCreateTimeBefore(
OrderStatus.UNPAID,
LocalDateTime.now().minusMinutes(30));
orders.forEach(order -> {
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
log.warn("订单[{}]因超时未支付已自动取消", order.getId());
});
}
}
2.2 动态延迟任务实现
固定延迟无法满足动态配置需求,可通过ScheduledTaskRegistrar实现:
java复制@Configuration
@EnableScheduling
public class DynamicSchedulerConfig implements SchedulingConfigurer {
@Autowired
private OrderConfig orderConfig;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
// 任务内容
() -> checkTimeoutOrders(),
// 触发器
triggerContext -> {
// 从数据库或配置中心获取最新延迟时间
long delay = orderConfig.getCancelDelayMinutes();
return new Date(System.currentTimeMillis() + delay * 60 * 1000);
}
);
}
private void checkTimeoutOrders() {
// 具体业务逻辑
}
}
2.3 异步任务执行优化
默认情况下所有@Scheduled任务共享单线程池,可能导致任务堆积。Spring Boot 3中可自定义线程池:
yaml复制# application.yml
spring:
task:
scheduling:
thread-name-prefix: scheduled-task-
pool:
size: 10
或通过编程方式配置:
java复制@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(60);
return scheduler;
}
3. 基于Redis的分布式延时方案
3.1 ZSET有序集合方案
利用Redis的ZSET数据结构,以时间戳作为score实现延时队列:
java复制@Component
public class RedisDelayQueue {
private final RedisTemplate<String, String> redisTemplate;
private static final String DELAY_QUEUE_KEY = "delay_queue:order_cancel";
public void addTask(String orderId, long delaySeconds) {
double executeTime = System.currentTimeMillis() + delaySeconds * 1000;
redisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, orderId, executeTime);
}
@Scheduled(fixedRate = 5000) // 每5秒扫描一次
public void processDelayTasks() {
long now = System.currentTimeMillis();
Set<String> tasks = redisTemplate.opsForZSet()
.rangeByScore(DELAY_QUEUE_KEY, 0, now);
tasks.forEach(task -> {
// 处理任务逻辑
handleCancelOrder(task);
// 从队列移除
redisTemplate.opsForZSet().remove(DELAY_QUEUE_KEY, task);
});
}
}
3.2 Redisson延迟队列方案
Redisson提供了更完善的RDelayedQueue实现:
java复制@Configuration
public class RedissonDelayConfig {
@Bean
public RDelayedQueue<String> orderDelayQueue(RedissonClient redisson) {
RQueue<String> destinationQueue = redisson.getQueue("order_cancel_queue");
return redisson.getDelayedQueue(destinationQueue);
}
}
@Service
@RequiredArgsConstructor
public class OrderCancelService {
private final RDelayedQueue<String> orderDelayQueue;
public void delayCancelOrder(String orderId, long delay, TimeUnit unit) {
orderDelayQueue.offer(orderId, delay, unit);
}
@PostConstruct
public void startConsumer() {
new Thread(() -> {
RQueue<String> queue = orderDelayQueue.getQueue();
while (true) {
try {
String orderId = queue.take();
handleCancelOrder(orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
}
}
4. 消息队列方案实现
4.1 RabbitMQ死信队列方案
配置死信交换机和队列:
java复制@Configuration
public class RabbitMQConfig {
// 主业务交换机
@Bean
public DirectExchange orderExchange() {
return new DirectExchange("order.exchange");
}
// 延时队列(设置TTL和死信路由)
@Bean
public Queue orderDelayQueue() {
return QueueBuilder.durable("order.delay.queue")
.withArgument("x-dead-letter-exchange", "order.process.exchange")
.withArgument("x-dead-letter-routing-key", "order.cancel")
.build();
}
// 实际处理队列
@Bean
public Queue orderProcessQueue() {
return new Queue("order.process.queue");
}
// 绑定关系
@Bean
public Binding delayBinding() {
return BindingBuilder.bind(orderDelayQueue())
.to(orderExchange())
.with("order.create");
}
}
发送延时消息:
java复制public void sendDelayMessage(String orderId, long delayMinutes) {
rabbitTemplate.convertAndSend(
"order.exchange",
"order.create",
orderId,
message -> {
message.getMessageProperties()
.setExpiration(String.valueOf(delayMinutes * 60 * 1000));
return message;
}
);
}
4.2 RocketMQ延迟消息方案
RocketMQ原生支持18个延迟级别:
java复制@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@PostMapping("/create")
public String createOrder(@RequestBody Order order) {
// 保存订单
orderService.save(order);
// 发送延迟消息(延迟级别3对应10秒)
rocketMQTemplate.syncSend("order_topic",
MessageBuilder.withPayload(order.getId())
.setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3")
.build());
return "success";
}
}
消费者配置:
java复制@RocketMQMessageListener(
topic = "order_topic",
consumerGroup = "order_cancel_group"
)
@Service
public class OrderCancelConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String orderId) {
orderService.cancelExpiredOrder(orderId);
}
}
5. 生产环境注意事项
5.1 任务幂等性设计
所有延时任务必须实现幂等处理:
java复制public void handleCancelOrder(String orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
// 状态校验防止重复处理
if (order.getStatus() != OrderStatus.UNPAID) {
return;
}
// 乐观锁控制
int updated = orderRepository.updateOrderStatus(
orderId,
OrderStatus.UNPAID,
OrderStatus.CANCELLED);
if (updated == 0) {
log.warn("订单[{}]状态变更冲突,可能已被其他进程处理", orderId);
}
}
5.2 故障恢复机制
建议采用任务状态机+补偿机制:
- 任务创建时记录到数据库
delay_task表 - 任务执行后更新状态为"PROCESSED"
- 定时任务扫描超时未处理的"PENDING"状态任务
- 添加重试次数限制和告警机制
5.3 监控指标建设
关键监控指标示例:
- 任务积压量(Redis ZSET大小/RabbitMQ队列深度)
- 任务处理耗时(从计划执行到实际执行的延迟)
- 任务成功率/失败率
- 系统资源占用(线程池使用率、内存消耗)
Spring Boot Actuator集成示例:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "order-service",
"module", "delay-task"
);
}
@Timed(value = "delay.task.process", description = "延时任务处理耗时")
@Counted(value = "delay.task.count", description = "延时任务执行次数")
public void processTask(String taskId) {
// 任务处理逻辑
}
6. 方案对比与选型建议
| 方案类型 | 精度 | 可靠性 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Spring Scheduling | 秒级 | 中 | 低 | 单机简单场景 |
| Redis ZSET | 秒级 | 中 | 中 | 分布式轻量级延迟 |
| RabbitMQ DLX | 分钟级 | 高 | 高 | 需要持久化的业务场景 |
| RocketMQ Delay | 固定级别 | 高 | 中 | 已使用RocketMQ的系统 |
| Quartz Cluster | 毫秒级 | 高 | 高 | 复杂调度需求 |
选型决策树:
- 是否需要持久化?是 → 考虑消息队列或Quartz
- 是否分布式环境?是 → 排除纯Spring Scheduling
- 延迟精度要求?秒级 → Redis/Quartz,分钟级 → 消息队列
- 是否已有中间件?优先使用现有基础设施
在Spring Boot 3项目中,我的实践经验是:
- 对于简单的单机延迟需求,使用
@Scheduled+线程池配置足够 - 分布式环境首选Redis方案,兼顾性能和实现复杂度
- 关键业务场景建议采用RabbitMQ死信队列保证可靠性
- 已有RocketMQ的系统直接使用其延迟消息功能最便捷