1、扫表轮循
定时任务 => 获取数据 => 数据层 => 筛选出过期的数据 => 批量关闭超时订单
优点:实现简单、适用于小项目、数据量比较少
缺点:订单量过大的时候查询和修改数据库压力大、服务器内存消耗大、IO瓶颈
2、Redis懒删除
用户获取订单信息 => 判断是否过期 => 关闭超时订单
优点:实现简单
缺点:必须要用户查询该条订单信息的时候才会触发
3、RabbitMQ或Kafka延迟消息队列
用户下单 => 发送延迟消息 => 延迟队列存储 => 到期消费 => 检测支付状态 => 未支付? => 关闭超时订单
缺点:需要引入消息中间件
优点:随时能从队列里面移除实时取消的订单、不会占用应用服务器的资源、异步化处理
SpringBoot + RabbitMQ延迟消息队列实现(死信队列)
死信,顾名思义就是无法被消费的消息。一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致queu 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
而由于TTL(生存时间)过期导致的死信,就是我们实现延迟队列的的方式。
也就是说我们的正常的队列中某个值达到了特定的时间,过期了,这时候这个值将会到达死信队列中去,我们只需要监听死信队列就可以操作订单支付超时自动关闭的业务了。
1、pom.xml中引入RabbitMQ
<!-- springboot集成rabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、application.yml中配置RabbitMQ
#rabbitmq配置
spring:
rabbitmq:
host: 106.14.34.213
port: 5672
username: ckm
password: ckm
virtual-host: /
3、配置RabbitMQConfig交换机和队列
package com.ckm.yangle.rabbitmqjob.config;
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;
//rabbitMQ绑定交换机 / 队列
@Configuration
public class RabbitMQConfig {
//========================================================订单未支付定时关闭RabbitMQ Queue========================================================//
//订单交换机
@Bean
public DirectExchange directExchangeOrder(){
return new DirectExchange("order-direct-exchange");
}
//订单队列
@Bean
public Queue orderDataChangeQueue() {
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定到死信交换机
args.put("x-dead-letter-exchange", "order-dead-direct-exchange");
//声明当前队列绑定到死信路由 key
args.put("x-dead-letter-routing-key", "routing-order-dead");
//声明队列的 TTL 过期时间 设置队列的过期时间为10秒(10000),单位为毫秒
args.put("x-message-ttl", 10000);
return QueueBuilder.durable("order-queue").withArguments(args).build();
}
//队列绑定交换机
@Bean
public Binding orderBindExchange(Queue orderDataChangeQueue, DirectExchange directExchangeOrder) {
return BindingBuilder.bind(orderDataChangeQueue).to(directExchangeOrder).with("routing-order");
}
// 声明 死信队列交换机 order-dead-direct-exchange
@Bean
public DirectExchange directExchangeOrderDead() {
return new DirectExchange("order-dead-direct-exchange");
}
//声明死信队列 order-dead-queue
@Bean
public Queue orderDeadDataChangeQueue() {
return new Queue("order-dead-queue",true);
}
//声明死信队列绑定死信交换机
@Bean
public Binding orderDeadBindExchange(Queue orderDeadDataChangeQueue, DirectExchange directExchangeOrderDead) {
return BindingBuilder.bind(orderDeadDataChangeQueue).to(directExchangeOrderDead).with("routing-order-dead");
}
}
4、监听RabbitMQ死信队列消费者消费
package com.ckm.yangle.rabbitmqjob.order;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Date;
@Slf4j
@Component
public class RabbitMQDataSyncListenerOrder {
@RabbitListener(queues = "order-dead-queue")
public void receiveD(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列信息:{}", new Date().toString(), msg);
}
}
5、在业务中生产者生产消息
log.info("当前时间:{},发送一条信息给一个 TTL 队列:{}", new Date(), "消息测试");
rabbitTemplate.convertAndSend("order-direct-exchange", "routing-order", "消息来自 ttl 为 30S 的队列: " + "消息测试");
6、测试结果
业务执行生产消息 => 到达过期时间 => 监听到死信队列
4、Redis过期监听机制
用户下单 => 发送Redis消息 => Redis Key过期时间 => 过期回调 => 检测支付状态 => 未支付?关闭超时订单
缺点:订单量过大非常消耗Redis服务器资源
优点:不会占用应用服务器的资源、应用的宕机不会对Redis产生影响、Redis过期时间准确度高