目录
[2.1.3.1Spring Scheduler(单机简单任务)](#2.1.3.1Spring Scheduler(单机简单任务))
[(2)集成执行器(Spring Boot项目)](#(2)集成执行器(Spring Boot项目))
[① 引入依赖](#① 引入依赖)
[② 配置执行器(application.yml)](#② 配置执行器(application.yml))
[③ 配置执行器客户端](#③ 配置执行器客户端)
[④ 实现XXL-Job任务](#④ 实现XXL-Job任务)
[⑤ 调度中心配置任务](#⑤ 调度中心配置任务)
[2.5RabbitMQ 安装延迟队列](#2.5RabbitMQ 安装延迟队列)
(2).代码示例.代码示例)
1.引入
日常开发中,我们经常遇到这种业务场景,如:外卖订单超 30 分钟未支付,则自动取订单;用户注册成功 15 分钟后,发短信息通知用户等等。这就延时任务处理场景。在电商,支付等系统中,一设都是先创建订单(支付单),再给用户一定的时间进行支付,如果没有按时支付的话,就需要把之前的订单(支付单)取消掉。这种类以的场景有很多,还有比如到期自动收货,超时自动退款,下单后自动发送短信等等都是类似的业务问题。
2.解决方案
2.1定时任务
2.1.1定时任务概述
定时任务本质是"按预设时间规则自动执行的代码逻辑",其核心价值在于"解放人工、保障业务定时闭环"。常见应用场景可分为5类:
- 数据治理 类:每日凌晨清理过期日志、无效订单;定期归档历史数据(如将3个月前的订单数据迁移至历史库);
- 业务闭环类:订单创建后30分钟未支付自动关闭;会员到期前3天发送续费提醒;
- 统计报表类:每日凌晨生成前一天的销售报表、用户活跃报表;每月生成月度经营分析报告;
- 系统运维类:定时检查服务健康状态;定期备份数据库;清理缓存碎片;
- 消息推送类:定时推送每日早安消息、营销活动通知;定时同步第三方数据(如同步物流信息)。
不同场景对定时任务的要求不同:简单场景只需"按时执行",复杂场景则需要考虑"高可用、无重复、可监控、可重试"。
2.1.2定时任务方案对比
后端定时任务的实现方案有很多,从简单到复杂可分为3个层级,不同方案适配不同的业务规模:
| 实现方案 | 核心优势 | 局限性 | 适用场景 |
|---|---|---|---|
| Spring Scheduler(@Scheduled) | 1. 零依赖,Spring Boot原生支持;2. 配置简单,注解式开发;3. 轻量高效,无额外部署成本 | 1. 不支持分布式部署(易重复执行);2. 无内置监控、重试机制;3. 任务依赖、动态调整能力弱 | 单机部署、简单定时任务(如单机服务的日志清理) |
| Quartz | 1. 功能强大,支持复杂调度规则(如日历式调度);2. 支持持久化(任务信息存入数据库);3. 支持集群部署(解决重复执行);4. 支持任务优先级、重试 | 1. 配置复杂,代码侵入性较强;2. 无内置监控告警,需自行实现;3. 分布式场景下运维成本较高 | 单机/集群部署、复杂调度规则的任务(如按工作日执行、任务依赖) |
| XXL-Job(分布式任务调度平台) | 1. 分布式部署无重复执行;2. 内置Web管理界面(任务配置、执行日志、监控告警);3. 支持动态调整任务、失败重试、任务依赖;4. 支持分片执行(大数据量处理) | 1. 需额外部署调度中心;2. 轻度依赖中间件(MySQL);3. 简单场景略显重量级 | 分布式微服务架构、高可用要求高的核心业务任务(如订单超时关闭、报表生成) |
选型建议: 1. 简单场景(单机):优先用 Spring Scheduler,快速落地无成本; 2. 复杂调度(单机/小规模集群):用 Quartz,兼顾功能与稳定性; 3. 分布式微服务(大规模、高可用):用 XXL-Job,解放运维与开发效率。
2.1.3定时任务方案实现
2.1.3.1Spring Scheduler(单机简单任务)
Spring Scheduler是Spring框架内置的定时任务组件,无需额外引入依赖,注解式开发即可快速实现。
①.环境准备:
在Spring Boot主类上添加 @EnableScheduling 注解,启用定时任务功能:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // 启用定时任务
public class TimedTaskDemoApplication {
public static void main(String[] args) {
SpringApplication.run(TimedTaskDemoApplication.class, args);
}
}
②.业务代码:
创建定时任务类,通过 @Scheduled 注解定义任务执行规则,支持3种常见时间配置:cron表达式、固定速率、固定延迟。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* Spring Scheduler 定时任务示例
*/
@Component
public class SimpleTimedTask {
private final Logger log = LoggerFactory.getLogger(SimpleTimedTask.class);
/**
* 1. cron表达式:按指定时间执行(每天凌晨3点清理过期订单)
* cron语法:秒 分 时 日 月 周 年(年可选)
* 示例:0 0 3 * * ? 表示每天03:00:00执行
*/
@Scheduled(cron = "0 0 3 * * ?")
public void cleanExpiredOrder() {
// 手动绑定requestId到MDC,便于日志追踪(参考之前MDC博客内容)
String requestId = UUID.randomUUID().toString().replace("-", "");
MDC.put("requestId", requestId);
try {
log.info("开始清理过期订单");
// 核心业务逻辑:删除创建时间超过30分钟且未支付的订单
// orderMapper.deleteExpiredOrder(30);
log.info("过期订单清理完成");
} catch (Exception e) {
log.error("清理过期订单失败", e);
} finally {
MDC.clear(); // 清除MDC,避免线程复用污染
}
}
/**
* 2. 固定速率:按固定时间间隔执行(每隔5秒打印系统时间)
* fixedRate:以上一次任务开始时间为基准,间隔固定时间执行
*/
@Scheduled(fixedRate = 5000)
public void printSystemTime() {
log.info("当前系统时间:{}", System.currentTimeMillis());
}
/**
* 3. 固定延迟:按固定延迟执行(上一次任务结束后,间隔固定时间再执行)
* fixedDelay:适合任务执行时间不固定的场景(如数据同步,执行时间可能波动)
*/
@Scheduled(fixedDelay = 10000)
public void syncThirdPartyData() {
log.info("开始同步第三方物流数据");
try {
// 模拟同步耗时(1-3秒)
Thread.sleep((long) (Math.random() * 2000 + 1000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("第三方物流数据同步完成");
}
}
关闭订单:
通过定时任务关闭订单,是一种成本很低,实现也很容易的方案。通过简单的几行代码,
写一个定时任务,定期扫描数据库中的订单,如果时间过期,就将其状态更新为关闭即
可。

优点:
实现容易,成本低,基本不依赖其他组件。
缺点:
时间可能不够精确。由于定时任务扫描的间隔是固定的,所以可能造成一些订单已经过期了一段时间才被扫描到,订单关闭的时间比正常时间晚一些。增加了数据库的压力。随着订单的数量越来越多,扫描的成本也会越来越大,执行时间也会被拉长,可能导致某些应该被关闭的订单迟迟没有被关闭。
总结 :采用定时任务的方案比较适合对时间要求不是很敏感,并且数据量不太多的业务场景。
③自定义线程池(可选)
Spring Scheduler默认使用单线程执行所有定时任务,若多个任务同时触发,会导致任务阻塞(一个任务执行慢,后续任务排队)。建议自定义线程池,提高任务并发能力:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync // 启用异步执行(可选,结合@Async使用)
public class SchedulerConfig {
/**
* 自定义定时任务线程池
*/
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // 核心线程数
scheduler.setThreadNamePrefix("scheduler-thread-"); // 线程名前缀
scheduler.setAwaitTerminationSeconds(60); // 等待任务执行完成的时间
scheduler.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略(调用者执行,避免任务丢失)
scheduler.initialize();
return scheduler;
}
}
2.1.3.2Quartz(复杂调度+集群部署)
Quartz是功能强大的开源定时任务框架,支持复杂调度规则、任务持久化和集群部署,适合需要"高可靠性"和"复杂调度"的场景。
①.引入依赖
<!-- Quartz核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 数据库依赖(用于任务持久化) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
②.配置Quartz(application.yml)
配置Quartz的数据源(任务信息存入数据库)、线程池、集群模式:
spring:
# 数据源配置(任务持久化到MySQL)
datasource:
url: jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Quartz配置
quartz:
job-store-type: JDBC # 任务存储方式:JDBC(持久化)
jdbc:
initialize-schema: NEVER # 不自动初始化表结构(建议手动执行Quartz官方SQL)
properties:
org:
quartz:
scheduler:
instanceName: quartzScheduler # 调度器实例名
instanceId: AUTO # 实例ID自动生成(集群模式必须)
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10 # 线程池大小
threadPriority: 5 # 线程优先级
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX # 事务型存储
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # MySQL delegate
tablePrefix: QRTZ_ # 表前缀(Quartz官方表的默认前缀)
isClustered: true # 启用集群模式
clusterCheckinInterval: 15000 # 集群节点心跳间隔(15秒)
useProperties: false # 不使用属性文件存储任务参数
③.Quartz的任务需实现 Job 接口,重写 execute 方法:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.UUID;
/**
* Quartz任务:生成每日销售报表
*/
public class DailySalesReportJob implements Job {
private final Logger log = LoggerFactory.getLogger(DailySalesReportJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 绑定MDC链路标识
String requestId = UUID.randomUUID().toString().replace("-", "");
MDC.put("requestId", requestId);
try {
log.info("开始生成每日销售报表");
// 核心业务逻辑:查询前一天的销售数据,生成Excel报表并存储
// String reportPath = salesReportService.generateDailyReport();
// log.info("每日销售报表生成完成,路径:{}", reportPath);
} catch (Exception e) {
log.error("生成每日销售报表失败", e);
// 若任务失败,可抛出异常触发重试(需配置重试策略)
throw new JobExecutionException("报表生成失败,触发重试", e, true);
} finally {
MDC.clear();
}
}
}
④.配置任务触发器(Trigger)
通过配置类创建JobDetail和Trigger,定义任务执行规则:
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzJobConfig {
/**
* 1. 定义JobDetail(任务详情)
*/
@Bean
public JobDetail dailySalesReportJobDetail() {
return JobBuilder.newJob(DailySalesReportJob.class)
.withIdentity("dailySalesReportJob", "reportGroup") // 任务标识(名称+组名)
.storeDurably() // 任务即使没有触发器也持久化
.build();
}
/**
* 2. 定义Trigger(触发器):每天凌晨2点执行
*/
@Bean
public Trigger dailySalesReportTrigger() {
// 调度规则:每天02:00:00执行
CronScheduleBuilder cronSchedule = CronScheduleBuilder.cronSchedule("0 0 2 * * ?")
.withMisfireHandlingInstructionDoNothing(); // 错过执行时间时,不执行(避免重复执行)
return TriggerBuilder.newTrigger()
.forJob(dailySalesReportJobDetail()) // 绑定任务
.withIdentity("dailySalesReportTrigger", "reportGroup") // 触发器标识
.withSchedule(cronSchedule) // 绑定调度规则
.build();
}
}
2.1.3.3XXL-Job(分布式任务调度平台)
XXL-Job是国内开源的分布式任务调度平台,基于"调度中心+执行器"架构,提供Web管理界面、任务监控、失败重试、分片执行等功能,是分布式微服务架构的首选方案。
(1)部署调度中心
从XXL-Job官网(https://www.xuxueli.com/xxl-job/)下载源码,执行源码中 doc/db/tables_xxl_job.sql SQL文件,创建调度中心数据库; 2. 修改调度中心配置文件(xxl-job-admin/src/main/resources/application.properties),配置数据库连接; 3. 启动调度中心(Spring Boot应用),访问 http://localhost:8080/xxl-job-admin,默认账号密码:admin/123456。
(2)集成执行器(Spring Boot项目)
① 引入依赖
代码语言:javascript
AI代码解释
<!-- XXL-Job执行器依赖 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>
② 配置执行器(application.yml)
xxl:
job:
admin:
addresses: http://localhost:8080/xxl-job-admin # 调度中心地址
executor:
appname: order-service-executor # 执行器名称(需在调度中心注册)
address: "" # 执行器地址(空则自动注册)
ip: "" # 执行器IP(空则自动获取)
port: 9999 # 执行器端口
logpath: ./logs/xxl-job/ # 任务日志路径
logretentiondays: 30 # 日志保留天数
accessToken: "" # 调度中心与执行器的通信令牌(空则关闭)
③ 配置执行器客户端
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setAccessToken(accessToken);
return xxlJobSpringExecutor;
}
}
④ 实现XXL-Job任务
通过 @XxlJob 注解定义任务,任务逻辑写在注解指定的方法中:
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* XXL-Job任务:订单超时关闭
*/
@Component
public class OrderTimeoutCloseJob {
private final Logger log = LoggerFactory.getLogger(OrderTimeoutCloseJob.class);
/**
* 任务标识:orderTimeoutCloseHandler(需在调度中心配置对应Handler名称)
*/
@XxlJob("orderTimeoutCloseHandler")
public void orderTimeoutClose() {
log.info("开始执行订单超时关闭任务");
try {
// 核心业务逻辑:查询所有创建时间超过30分钟且未支付的订单,执行关闭操作
// int closeCount = orderService.closeTimeoutOrder(30);
// log.info("订单超时关闭任务执行完成,共关闭订单:{} 个", closeCount);
} catch (Exception e) {
log.error("订单超时关闭任务执行失败", e);
// 抛出异常,XXL-Job会自动记录失败状态,支持手动重试或配置自动重试
throw new RuntimeException("订单超时关闭失败", e);
}
}
}
⑤ 调度中心配置任务
- 登录XXL-Job管理界面,在"执行器管理"中添加执行器(AppName与配置文件一致); 2. 在"任务管理"中创建任务: - 任务描述:订单超时关闭; - 执行器:选择已注册的执行器; - 任务类型:BEAN模式; - JobHandler:填写注解中的任务标识(orderTimeoutCloseHandler); - 调度规则:cron表达式(如0 */1 * * * ? 表示每分钟执行一次); 3. 启动任务,即可实现分布式环境下的定时执行。
2.2JDK延迟队列DelayQueue

DelayQueue 是 JDK 提供的一个无界队列,我们可以看到,DelayQueue 队列中的元素需要实现 Delayed,它只提供了一个方法,就是获取过期时间。
用户的订单生成以后,设置过期时间比如 30 分钟,放入定义好的 DelayQueue,然
后创建一个线程,在线程中通过 while(true)不断的从 DelayQueue 中获取过期的数
据。
优点:不依赖任何第三方组件,连数据库也不需要了,实现起来也方便。
缺点:因为 DelayQueue 是一个无界队列,如果放入的订单过多,会造成 JVM OOM。DelayQueue 基于 JVM 内存,如果 JVM 重启了,那所有数据就丢失了。
总结 :DelayQueue 适用于数据量较小,且丢失也不影响主业务的场景,比如内部系
统的一些非重要通知,就算丢失,也不会有太大影响。
2.3redis过期监听
概述
redis 是一个高性能的 KV 数据库,除了用作缓存以外,其实还提供了过期监听的功能。
在 redis.conf 中,配置 notify-keyspace-events Ex 即可开启此功能。
然后在代码中继承 KeyspaceEventMessageListener,实现 onMessage 就可以监听
过期的数据量。
Redis配置
# 开启过期事件监听
redis-cli config set notify-keyspace-events Ex
监听器实现
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
@Component
public class RedisExpireListener extends KeyExpirationEventMessageListener {
public RedisExpireListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
// 获取过期的key
String expiredKey = new String(message.getBody());
// 只处理业务相关的key
if (!expiredKey.startsWith("task:")) {
return;
}
// 异步处理,避免阻塞监听线程
new Thread(() -> processExpiredKey(expiredKey)).start();
}
private void processExpiredKey(String expiredKey) {
System.out.println("处理过期任务: " + expiredKey);
// 这里写你的业务逻辑
// 例如:取消订单、发送通知等
}
}
服务类添加延时任务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DelayTaskService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 添加延时任务
* @param taskId 任务ID
* @param taskData 任务数据
* @param delaySeconds 延迟秒数
*/
public void addDelayTask(String taskId, String taskData, long delaySeconds) {
// 1. 存储任务数据
String dataKey = "task:data:" + taskId;
redisTemplate.opsForValue().set(dataKey, taskData);
// 2. 设置会过期的key(触发监听)
String expireKey = "task:" + taskId;
redisTemplate.opsForValue().set(expireKey, "pending", delaySeconds, TimeUnit.SECONDS);
System.out.println("添加延时任务: " + taskId + ", " + delaySeconds + "秒后执行");
}
}
控制器展示
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class TaskController {
@Autowired
private DelayTaskService delayTaskService;
@PostMapping("/addTask")
public String addTask(@RequestParam String taskId,
@RequestParam String taskData,
@RequestParam long delaySeconds) {
delayTaskService.addDelayTask(taskId, taskData, delaySeconds);
return "任务添加成功,将在" + delaySeconds + "秒后执行";
}
@PostMapping("/orderTimeout")
public String addOrderTimeout(@RequestParam String orderId,
@RequestParam(defaultValue = "1800") long delaySeconds) {
String taskId = "order:" + orderId;
String taskData = "{\"action\":\"cancel\",\"reason\":\"timeout\"}";
delayTaskService.addDelayTask(taskId, taskData, delaySeconds);
return "订单超时任务已添加: " + orderId;
}
}
2.4Redission分布式延迟队列
2.4.1概述
Redisson 是一个基于 redis 实现的 Java 驻内存数据网格,它不仅提供了一系列的分
布式的 Java 常用对象,还提供了许多分布式服务。Redisson 除了提供我们常用的分布式锁外,还提供了一个分布式延迟队列RDelayedQueue,他是一种基于 zset 结构实现的延迟队列,其实现类是RedissonDelayedQueue。

**优点:**使用简单,并且其实现类中大量使用 lua 脚本保证其原子性,不会有并发重复问题。
缺点:需要依赖 redis(如果这算一种缺点的话)。
总结:Redisson 是 redis 官方推荐的 JAVA 客户端,提供了很多常用的功能,使用
简单、高效,推荐大家尝试使用。
2.4.2示例
①配置延迟时间
spring:
redis:
host: localhost
port: 6379
order:
auto-close:
delay-minutes: 30 # 30分钟后自动关闭
②核心服务类
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Service
public class OrderAutoCloseService {
@Autowired
private RedissonClient redissonClient;
@Value("${order.auto-close.delay-minutes:30}")
private long delayMinutes;
private RDelayedQueue<String> delayedQueue;
private RBlockingQueue<String> blockingQueue;
@PostConstruct //实现依赖注入完成之后执行初始化的逻辑;
public void init() {
// 1. 获取阻塞队列和延迟队列
blockingQueue = redissonClient.getBlockingQueue("order:delay:queue");
delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
// 2. 启动消费者线程
startConsumer();
}
/**
* 添加订单到延迟队列
*/
public void addOrderToDelayQueue(String orderId) {
delayedQueue.offer(orderId, delayMinutes, TimeUnit.MINUTES);
System.out.println("订单 " + orderId + " 已加入延迟队列," + delayMinutes + "分钟后检查");
}
/**
* 启动消费者线程
*/
private void startConsumer() {
new Thread(() -> {
System.out.println("订单自动关闭消费者启动...");
while (true) {
try {
// 阻塞获取到期的订单ID
String orderId = blockingQueue.take();
// 处理订单
processOrder(orderId);
} catch (InterruptedException e) {
System.out.println("消费者线程被中断");
break;
}
}
}, "order-auto-close-consumer").start();
}
/**
* 处理到期的订单
*/
private void processOrder(String orderId) {
System.out.println("检查订单状态: " + orderId);
try {
// 1. 查询订单状态(这里模拟查询)
String orderStatus = getOrderStatus(orderId);
// 2. 如果未支付,关闭订单
if ("UNPAID".equals(orderStatus)) {
closeOrder(orderId);
System.out.println("订单 " + orderId + " 已自动关闭(未支付)");
} else {
System.out.println("订单 " + orderId + " 状态为: " + orderStatus + ",无需处理");
}
} catch (Exception e) {
System.err.println("处理订单失败: " + orderId + ", 错误: " + e.getMessage());
// 可以加入重试队列
}
}
/**
* 查询订单状态(模拟)
*/
private String getOrderStatus(String orderId) {
// 这里应该调用订单服务查询实际状态
// 模拟:80%的概率是未支付
return Math.random() > 0.2 ? "UNPAID" : "PAID";
}
/**
* 关闭订单(模拟)
*/
private void closeOrder(String orderId) {
// 这里应该调用订单服务执行关闭操作:
// 1. 更新订单状态为已关闭
// 2. 释放库存
// 3. 发送通知
// 4. 记录日志
System.out.println("执行关闭订单: " + orderId);
// 模拟业务处理时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* 取消延迟任务(用户已支付)
*/
public void cancelDelayTask(String orderId) {
// 从延迟队列中移除订单
boolean removed = delayedQueue.remove(orderId);
if (removed) {
System.out.println("订单 " + orderId + " 已从延迟队列移除(用户已支付)");
}
}
}
③.优缺点分析

2.5RabbitMQ 安装延迟队列
延迟消息,当消息写入到 Broker 后,不会立刻被消费者消费,需要等待指定的时
长后才可被消费处理的消息,称为延时消息。
(1).概述
在订单创建之后,我们就可以把订单作为一条消息投递到 rabbitmq,并将延迟时间设
置为 30 分钟,这样,30 分钟后我们定义的 consumer 就可以消费到这条消息,然
后检查用户是否支付了这个订单。通过延迟消息,我们就可以将业务解耦,极大地简化我们的代码逻辑。
(2).代码示例
①.配置类
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// 延迟交换机(需要安装rabbitmq_delayed_message_exchange插件)
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct"); // 延迟交换机的类型
return new CustomExchange(
"order.delayed.exchange", // 交换机名称
"x-delayed-message", // 交换机类型(必须)
true, // 是否持久化
false, // 是否自动删除
args
);
}
// 延迟队列
@Bean
public Queue orderCloseQueue() {
return QueueBuilder.durable("order.close.queue").build();
}
// 绑定队列到延迟交换机
@Bean
public Binding binding() {
return BindingBuilder.bind(orderCloseQueue())
.to(delayedExchange())
.with("order.close.key")
.noargs();
}
}
②.消息生产者
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderDelayProducer {
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送订单关闭延迟消息(30分钟后)
*/
public void sendOrderCloseMessage(String orderId) {
// 创建消息
OrderCloseMessage message = new OrderCloseMessage(orderId);
// 设置延迟时间(30分钟 = 1800000毫秒)
MessagePostProcessor processor = message1 -> {
message1.getMessageProperties().setDelay(1800000);
return message1;
};
// 发送消息
amqpTemplate.convertAndSend(
"order.delayed.exchange", // 交换机
"order.close.key", // 路由键
message, // 消息体
processor // 延迟处理器
);
System.out.println("发送订单关闭延迟消息: " + orderId);
}
}
@Data
@AllArgsConstructor
class OrderCloseMessage {
private String orderId;
private Long createTime = System.currentTimeMillis();
}
③.消息消费者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderCloseConsumer {
@Autowired
private OrderService orderService;
@RabbitListener(queues = "order.close.queue")
public void handleOrderClose(OrderCloseMessage message) {
System.out.println("收到订单关闭消息: " + message.getOrderId());
try {
// 查询订单状态
String orderId = message.getOrderId();
String status = orderService.getOrderStatus(orderId);
if ("UNPAID".equals(status)) {
// 关闭未支付订单
orderService.closeUnpaidOrder(orderId);
System.out.println("订单自动关闭成功: " + orderId);
} else {
System.out.println("订单状态为 " + status + ",无需关闭: " + orderId);
}
} catch (Exception e) {
System.err.println("处理订单关闭失败: " + e.getMessage());
}
}
}
④.订单服务类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderDelayProducer orderDelayProducer;
/**
* 创建订单
*/
public String createOrder() {
// 1. 生成订单ID
String orderId = "ORDER" + System.currentTimeMillis();
// 2. 保存订单到数据库(简化)
System.out.println("创建订单: " + orderId);
// 3. 发送30分钟延迟消息
orderDelayProducer.sendOrderCloseMessage(orderId);
return orderId;
}
/**
* 支付成功
*/
public void paySuccess(String orderId) {
System.out.println("订单支付成功: " + orderId);
// 更新订单状态...
}
/**
* 查询订单状态
*/
public String getOrderStatus(String orderId) {
// 模拟:80%概率未支付
return Math.random() > 0.2 ? "UNPAID" : "PAID";
}
/**
* 关闭未支付订单
*/
public void closeUnpaidOrder(String orderId) {
System.out.println("关闭未支付订单: " + orderId);
// 业务逻辑:更新状态、释放库存等
}
}
(3).优缺点分析
优点:可以使代码逻辑清晰,系统之间完全解耦,只需关注生产及消费消息即可。另外
其吞吐量极高,最多可以支撑万亿级的数据量。
缺点:相对来说 mq 是重量级的组件,引入 mq 之后,随之而来的消息丢失、幂等
性问题等都加深了系统的复杂度。
总结:通过 mq 进行系统业务解耦,以及对系统性能削峰填谷已经是当前高性能系统
的标配。
2.6死信队列
除了 RocketMQ 的延迟队列,RabbitMQ 的死信队列也可以实现消息延迟功能。
(1).概述
当 RabbitMQ 中的一条正常消息,因为过了存活时间(TTL 过期)、队列长度超限、
被消费者拒绝等原因无法被消费时,就会被当成一条死信消息,投递到死信队列。
基于这样的机制,我们可以给消息设置一个 ttl,然后故意不消费消息,等消息过期就
会进入死信队列,我们再消费死信队列即可。通过这样的方式,就可以达到同 RocketMQ 延迟消息一样的效果。
(2).演示
①.配置交换机路由关系
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
// 1. 订单交换机(普通交换机)
@Bean
public DirectExchange orderExchange() {
return new DirectExchange("order.exchange");
}
// 2. 死信交换机
@Bean
public DirectExchange orderDLXExchange() {
return new DirectExchange("order.dlx.exchange");
}
// 3. 延迟队列(30分钟TTL,过期后转到死信队列)
@Bean
public Queue orderDelayQueue() {
Map<String, Object> args = new HashMap<>();
// 设置死信交换机
args.put("x-dead-letter-exchange", "order.dlx.exchange");
// 设置死信路由键
args.put("x-dead-letter-routing-key", "order.close.key");
// 设置TTL:30分钟(1800000毫秒)
args.put("x-message-ttl", 1800000);
// 队列最大长度(防止内存溢出)
args.put("x-max-length", 10000);
return new Queue("order.delay.queue", true, false, false, args);
}
// 4. 死信队列(实际消费队列)
@Bean
public Queue orderCloseQueue() {
return new Queue("order.close.queue", true);
}
// 5. 绑定延迟队列到普通交换机
@Bean
public Binding delayBinding() {
return BindingBuilder.bind(orderDelayQueue())
.to(orderExchange())
.with("order.delay.key");
}
// 6. 绑定死信队列到死信交换机
@Bean
public Binding closeBinding() {
return BindingBuilder.bind(orderCloseQueue())
.to(orderDLXExchange())
.with("order.close.key");
}
}
②.生产者
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderDelayProducer {
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送订单延迟消息
* 消息会先进入order.delay.queue队列,30分钟后过期
* 过期后转到order.close.queue队列被消费
*/
public void sendOrderCloseMessage(String orderId) {
// 创建消息内容
String message = "订单ID:" + orderId;
// 发送到延迟队列
amqpTemplate.convertAndSend(
"order.exchange", // 交换机
"order.delay.key", // 路由键
message // 消息内容
);
System.out.println("发送订单延迟消息: " + orderId);
}
}
③.消费者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class OrderCloseConsumer {
/**
* 监听死信队列(订单关闭队列)
* 消息在延迟队列中30分钟后,会转到这个队列
*/
@RabbitListener(queues = "order.close.queue")
public void handleOrderClose(String message) {
System.out.println("收到订单关闭消息: " + message);
// 提取订单ID(实际项目中应该用JSON解析)
String orderId = message.replace("订单ID:", "");
try {
// 检查订单状态
String status = checkOrderStatus(orderId);
if ("UNPAID".equals(status)) {
// 执行订单关闭逻辑
closeOrder(orderId);
System.out.println("订单自动关闭成功: " + orderId);
} else {
System.out.println("订单已支付,无需关闭: " + orderId);
}
} catch (Exception e) {
System.err.println("处理订单失败: " + orderId + ", 错误: " + e.getMessage());
}
}
private String checkOrderStatus(String orderId) {
// 模拟查询订单状态
// 实际应该查询数据库
return Math.random() > 0.2 ? "UNPAID" : "PAID";
}
private void closeOrder(String orderId) {
System.out.println("执行订单关闭: " + orderId);
// 实际业务逻辑:
// 1. 更新订单状态为已关闭
// 2. 释放库存
// 3. 发送通知
}
}
④.订单服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderDelayProducer orderDelayProducer;
/**
* 创建订单
*/
public String createOrder() {
// 1. 生成订单ID
String orderId = generateOrderId();
// 2. 保存订单到数据库(简化)
System.out.println("创建订单: " + orderId);
// 3. 发送延迟消息(30分钟后检查是否支付)
orderDelayProducer.sendOrderCloseMessage(orderId);
return orderId;
}
/**
* 支付成功
*/
public void paySuccess(String orderId) {
System.out.println("订单支付成功: " + orderId);
// 更新订单状态为已支付
// 注意:死信队列消息无法取消,需要在消费时判断
}
private String generateOrderId() {
return "ORDER_" + System.currentTimeMillis();
}
}
(3).分析
优点:同 RocketMQ 一样,RabbitMQ 同样可以使业务解耦,基于其集群的扩展性,
也可以实现高可用、高性能的目标。
缺点:死信队列本质还是一个队列,队列都是先进先出,如果队头的消息过期时间比较
长,就会导致后面过期的消息无法得到及时消费,造成消息阻塞。