定时关闭订单
1、定时关单
1.0、业务流程
创建交换机、队列以及之间的绑定
在订单创建成功时向MQ中 延时队列发送消息
在订单的关闭之后时向MQ发送消息
监听 order.release.order.queue
队列,释放订单服务 关闭订单
监听 stock.release.stock.queue
队列,解锁库存 解锁库存
消息共享封装To
在订单创建成功时向MQ中 延时队列发送消息,携带路由键:order.create.order
30分钟后未支付,则释放订单服务向MQ中发送消息,携带路由键:order.release.order
路由消息到正常队列order.release.order.queue ,进行释放订单服务
此时存在一种情况,存在订单创建成功之后出现延时卡顿,消息延迟,导致订单解锁在库存解锁之后完成
则每次库存解锁之后向MQ中发送消息,携带路由键:order.release.other
监听 stock.release.stock.queue ,编写一个重载方法,进行判断
查一下最新库存的状态,防止重复解锁库存
按照工作单找到所有 没有解锁的库存,进行解锁
1.1、创建交换机、队列以及之间的绑定
package com.atguigu.gulimall.order.config;
@Configuration
public class MyMQConfig {
/**
* Spring中注入Bean之后,容器中的Binding、Queue、Exchange 都会自动创建(前提是RabbitMQ中没有)
* RabbitMQ 只要有,@Bean属性发生变化也不会覆盖
* @return
* Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
*/
@Bean
public Queue orderDelayQueue(){
HashMap<String, Object> arguments = new HashMap<>();
/**
* x-dead-letter-exchange :order-event-exchange 设置死信路由
* x-dead-letter-routing-key : order.release.order 设置死信路由键
* x-message-ttl :60000
*/
arguments.put("x-dead-letter-exchange","order-event-exchange");
arguments.put("x-dead-letter-routing-key","order.release.order");
arguments.put("x-message-ttl",30000);
Queue queue = new Queue("order.delay.queue", true, false, false,arguments);
return queue;
}
@Bean
public Queue orderReleaseOrderQueue(){
return new Queue("order.release.order.queue", true, false, false);
}
@Bean
public Exchange orderEventExchange(){
// TopicExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)
return new TopicExchange("order-event-exchange",true,false);
}
@Bean
public Binding orderCreateOrder(){
// Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map<String, Object> arguments)
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.create.order",
null);
}
@Bean
public Binding orderReleaseOrder(){
// Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map<String, Object> arguments)
return new Binding("order.release.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order",
null);
}
/**
* 订单释放直接和库存释放进行绑定
* @return
*/
@Bean
public Binding orderReleaseOtherBingding(){
// Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map<String, Object> arguments)
return new Binding("stock.release.stock.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.other.#",
null);
}
}
1.2、在订单创建成功时向MQ中 延时队列发送消息
1.3、在订单的关闭之后时向MQ发送消息
为了防止因为其他原因,订单的关闭延期了
/*** 订单的关闭
* @param entity
*/
@Override
public void closeOrder(OrderEntity entity) {
// 1、查询当前这个订单的最新状态
OrderEntity orderEntity = this.getById(entity.getId());
if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()) {
// 2、关单
OrderEntity update = new OrderEntity();
update.setId(entity.getId());
update.setStatus(OrderStatusEnum.CANCLED.getCode());
this.updateById(update);
OrderTo orderTo = new OrderTo();
BeanUtils.copyProperties(orderEntity, orderTo);
// 3、发给MQ一个
rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);
}
}
1.4、监听 order.release.order.queue
队列,释放订单服务
gulimall-order 服务的 com.atguigu.gulimall.order.listener
路径下的 OrderClassListener类。
package com.atguigu.gulimall.order.listener;
@RabbitListener(queues = "order.release.order.queue")
@Service
public class OrderClassListener {
@Autowired
OrderService orderService;
@RabbitHandler
public void listener(OrderEntity entity, Channel channel, Message message) throws IOException {
System.out.println("收到过期的订单信息:准备关闭订单!" + entity.getOrderSn());
try {
orderService.closeOrder(entity);
//肯定,让broker将移除此消息,以后不用重试。
//参数deliveryTag号是消息的唯一标识,参数false代表不批量确认此deliveryTag编号之前的所有消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e){
//拒绝;参数deliveryTag号是消息的唯一标识;参数true代表重新回队列,如果false则代表丢弃
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
}
2、如何保证消息可靠性
柔性事务-可靠消息+最终一致性方案(异步确保型)
-
防止消息丢失
- 做好消息确认机制(pulisher、consumer[手动ACK])
- 每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍
2.1、消息丢失问题,使用手动ack解决
消息发送出去,由于网络问题没有抵达服务器
做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式;
做好日志记录,每个消息状态是否都被服务器收到都应该记录;
做好定期重发,如果消息没有发生成,定期去数据库扫描未成功的消息进行重发;
消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此时Broker尚未持久化完成,宕机
Publisher 也必须加入确认回调机制,确认成功的消息,修改数据库消息状态
自动ACK的状态下。消费者收到消息,但没来得及消费然后宕机
一定开启手动ACK,消费成功才移除,失败或者没来得处理就noAck并重新入队
解决办法:
1、做好消息确认机制(pulisher、consumer[手动ACK])
2、每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍
2.2、消息重复问题,使用幂等性解决
消息重复
消息消费成功,事务已经提交,ack时,机器宕机,中间人以为消息没发成功,就重新发消息,导致消息重复问题。
导致没有ack成功Broker的消息重新由unack变为ready,并发送给其他消费者消息消费失败,由于重试机制,自动又将消息发送出去成功消费,ack时宕机,消息由unack变为ready,Broker又重新发送。
解决办法:接口设计为幂等性的。
消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志使用防重表(redis/mysql),发送消息每一个都有业务的唯一标识存到redis里,处理过就不用处
rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的
2.3、消息积压问题,使用增加消费者、解决
常见消息积压问题:
消费者宕机积压
消费者消费能力不足积压
发送者发送流量太大
解决办法:
上线更多的消费者,进行正常消费。
上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理
参考链接:https://blog.csdn.net/qq_40991313/article/details/129940364
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)