作者:浅小影HLGC_215 | 来源:互联网 | 2023-05-30 14:27
场景:在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内用户没有支付,则默认订单取消。如何订单超时实现?定时任务redission延时任务ra
场景:在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内
用户没有支付,则默认订单取消。
如何订单超时实现?
- 定时任务
- redission延时任务
- rabbitmq死信队列
本文将以rabbitmq死信队列展开做讲解,因为定时任务的方式,是有点问题的,原本业务系统希望10分钟后,如果订单未支付,就马上取消订单,并释放商品库存。但是一旦数据量大的话,就会加长获取未支付订单数据的时间,部分订单就做不到10分钟后取消了,可能是15分钟,20分钟之类的。这样的话,库存就无法及时得到释放,也就会影响成单数。而使用rabbitmq死信队列,在定义业务队列时可以考虑指定一个死信交换机,并绑定一个死信队列。当消息变成死信时,该消息就会被发送到该死信队列上,再取出订单信息进行判断订单是否已支付,如未支付则讲订单状态修改为取消状态,这样也是可以达到订单超时取消的需求的。
软件准备
erlang
请参考Win10下安装erlang
https://blog.csdn.net/linsongbin1/article/details/80170487
RabbitMQ
https://blog.csdn.net/linsongbin1/article/details/80170567
启动RabbitMQ,然后添加一个用户, 并给用户设置权限。
# 后台启动
rabbitmq-server -detached
# 添加用户
rabbitmqctl add_user root 123456
# 设置用户权限:
rabbitmqctl set_permissions -p "/" root ".*" ".*" ".*"
接下来创建一个springboot工程,并集成RabbitMQ。
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.8.RELEASE
com.lagou
rabbitmq-work
0.0.1-SNAPSHOT
rabbitmq-work
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
5.1.47
runtime
com.baomidou
mybatis-plus-boot-starter
3.3.2
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
接下来在application.yml配置文件中假如rabbitmq配置
spring:
application:
name: rabbit-work
# rabbitmq配置
rabbitmq:
host: localhost
virtual-host: /
username: root
password: 123456
# redis配置
redis:
timeout: 6000
host: localhost
port: 6379
database: 0
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
hikari:
minimum-idle: 3
maximum-pool-size: 5
max-lifetime: 30000
connection-init-sql: SELECT 1
# mybatis-plus配置
mybatis-plus:
mapper-locations: classpath:com.lagou.*.mapper/*.xml
type-aliases-package: com.lagou.*.domain
定义RabbitConfig
@Configuration
public class RabbitConfig {
/**
* 订单队列
*
* @return {@link Queue}
*/
@Bean
public Queue orderQueue() {
Map argments = new HashMap<>();
argments.put("x-message-ttl", 60000);
argments.put("x-dead-letter-exchange", RabbitConstants.ORDER_DLX_EXCHANGE);
argments.put("x-dead-letter-routing-key", RabbitConstants.ORDER_DLX_ROUTING_KEY);
Queue queue = new Queue(RabbitConstants.ORDER_QUEUE, true, false, false, argments);
return queue;
}
/**
* 订单交换机
*
* @return {@link Exchange}
*/
@Bean
public Exchange orderExchange() {
return new DirectExchange(RabbitConstants.ORDER_EXCHANGE, true, false, null);
}
/**
* 订单路由键
*
* @return {@link Binding}
*/
@Bean
public Binding orderRouting() {
return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(RabbitConstants.ORDER_ROUTING_KEY).noargs();
}
/**
* 订单死信队列
*
* @return {@link Queue}
*/
@Bean
public Queue orderDlxQueue() {
Queue queue = new Queue(RabbitConstants.ORDER_DLX_QUEUE, true, false, false);
return queue;
}
/**
* 订单死信交换机
*
* @return {@link Exchange}
*/
@Bean
public Exchange orderDlxExchange() {
return new DirectExchange(RabbitConstants.ORDER_DLX_EXCHANGE, true, false, null);
}
/**
* 订单死信路由键
*
* @return {@link Binding}
*/
@Bean
public Binding orderDlxRouting() {
return BindingBuilder.bind(orderDlxQueue()).to(orderDlxExchange()).with(RabbitConstants.ORDER_DLX_ROUTING_KEY).noargs();
}
/**
* 库存队列
*
* @return {@link Queue}
*/
@Bean
public Queue stockQueue() {
return new Queue(RabbitConstants.STOCK_QUEUE, true, false, false, null);
}
/**
* 库存交换机
*
* @return {@link Exchange}
*/
@Bean
public Exchange stockExchange() {
return new DirectExchange(RabbitConstants.STOCK_EXCHANGE, true, false, null);
}
/**
* 库存路由键
*
* @return {@link Binding}
*/
@Bean
public Binding stockRouting() {
return BindingBuilder.bind(stockQueue()).to(stockExchange()).with(RabbitConstants.STOCK_ROUTING_KEY).noargs();
}
}
实现消息发送
@RequestMapping(value = "/submit", produces = "application/json;charset=UTF-8")
public R submit(@RequestBody OrderVo orderVo) throws UnsupportedEncodingException {
Order order = orderService.createOrder(orderVo);
System.out.println("OrderController.submit... createOrder");
// 放入死信队列
amqpTemplate.convertAndSend(RabbitConstants.ORDER_EXCHANGE,
RabbitConstants.ORDER_ROUTING_KEY,
(order.getId() + ""));
System.out.println("OrderController.submit... sendMessage to orderExchange");
return R.ok(order);
}
消息消费者监听
@Component
public class OrderHandler {
@Autowired
private OrderService orderService;
@RabbitListener(queues = RabbitConstants.ORDER_DLX_QUEUE, ackMode = "MANUAL")
public void onMessage(Message message, Channel channel) throws IOException {
System.out.println("消息进入死信队列...");
String s = new String(message.getBody());
Order o = orderService.getById(Long.parseLong(s));
if (o != null && OrderConstants.TOPAID.getCode().equals(o.getOrderState())) {
Order order = new Order();
order.setId(o.getId());
order.setOrderState(OrderConstants.CANCEL.getCode());
order.setGmtModified(new Date());
orderService.updateById(order);
}
// 手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
启动Spring Boot应用进行测试
点击其中一个有库存的商品进行购买,先演示一个成功支付的。
接下来再演示支付失败的,并注意控制台日志打印。
通过监听死信队列,消息在进入死信队列之后就可以做一系列业务逻辑处理,比如,消息如果还是未支付状态,将其修改为取消支付。