百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

实战!SpringBoot + RabbitMQ死信队列实现超时关单

mhr18 2025-05-22 11:47 4 浏览 0 评论

需求背景之为什么要有超时关单

原因一:

第三方支付平台的支付连接都是有时效性,创建订单后,需要在一定的时间内支付完成, 比如微信支付、支付宝支付等。当然也可以不关闭订单,做订单二次支付的操作,但业务链路会更加复杂,所以一般会直接关闭

原因二:

电商业务里面还会涉及到商品库存的锁定和释放

所以多数订单业务都是会有这个功能,那如何设计呢?

RabbitMQ死信队列-延迟消息知识点回顾

什么是RabbitMQ的死信队列

没有被及时消费的消息存放的队列

什么是rabbitmq的死信交换机

Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机

消息有哪几种情况成为死信

消费者拒收消息(basic.reject/ basic.nack),并且没有重新入队 requeue=false 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live) 队列的消息长度达到极限 结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列

什么是延迟队列

一种带有延迟功能的消息队列,Producer 将消息发送到消息队列 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到Consumer 进行消费,该消息即延时消息

业界的一些实现方式:

  • 定时任务高精度轮训
  • redis监听key过期
  • jdk自带的DelayQueue
  • 采用RocketMQ自带延迟消息功能
  • RabbitMQ本身是不支持延迟队列的, 结合死信队列的特性,就可以做到延迟消息

代码实战环节

Rabbitmq 死信队列配置

import java.util.HashMap;
import java.util.Map;
 
/**
 *  * 自定义消息队列配置,
 *  * 发送 关单消息-》延迟exchange-》order.close.delay.queue-》死信exchange-》order.close.queue
 **/
 
@Configuration
@Data
public class RabbitMQConfig {
    /**
     * 交换机
     */
    private String orderEventExchange="order.event.exchange";
    /**
     * 延迟队列,不能被消费者监听
     */
    private String orderCloseDelayQueue = "order.close.delay.queue";
    /**
     * 关单队列,延迟队列的消息过期后转发的队列,用于被消费者监听
     */
    private String orderCloseQueue = "order.close.queue";
    /**
     * 进入到延迟队列的routingKey
     */
    private String orderCloseDelayRoutingKey = "order.close.delay.routing.key";
    /**
     * 进入死信队列的routingKey,消息过期进入死信队列的key
     */
    private String orderCloseRoutingKey = "order.close.routing.key";
    /**
     * 过期时间,毫秒单位,临时改为1分钟过期
     */
    private Integer ttl = 1000 * 60;
    /**
     * 消息转换器
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
    /**
     * 创建交换机,topic类型,一般一个业务一个交换机
     * @return
     */
    @Bean
    public Exchange orderEventExchange(){
        return new TopicExchange(orderEventExchange,true,false);
    }
    /**
     * 延迟队列getOrderEventExchange
     * @return
     */
    @Bean
    public Queue orderCloseDelayQueue(){
        Map<String,Object> args = new HashMap<>(3);
        args.put("x-dead-letter-exchange",orderEventExchange);
        args.put("x-dead-letter-routing-key",orderCloseRoutingKey);
        args.put("x-message-ttl",ttl);
        return new Queue(orderCloseDelayQueue,true,false,false,args);
 
    }
    /**
     * 死信队列,是一个普通队列,用于被监听
     * @return
     */
    @Bean
    public Queue orderCloseQueue(){
        return new Queue(orderCloseQueue,true,false,false);
    }
    /**
     * 第一个队列 即延迟队列和交换机建立绑定关系
     * @return
     */
    @Bean
    public Binding orderCloseDelayBinding(){
        return new Binding(orderCloseDelayQueue,
                Binding.DestinationType.QUEUE,orderEventExchange,orderCloseDelayRoutingKey,null);
    }
    /**
     * 死信队列和死信交换机建立绑定关系
     * @return
     */
    @Bean
    public Binding orderCloseBinding(){
 
        return new Binding(orderCloseQueue,
                Binding.DestinationType.QUEUE,orderEventExchange,orderCloseRoutingKey,null);
    }
 
 
}
##----------rabbit配置--------------
spring.rabbitmq.host=120.79.xxx.xxx
spring.rabbitmq.port=5672

#需要手工创建虚拟主机
spring.rabbitmq.virtual-host=dev
spring.rabbitmq.username=admin
spring.rabbitmq.password=password

#消息确认方式,manual(手动ack) 和auto(自动ack); 消息消费重试到达指定次数进到异常交换机和异常队列,需要改为自动ack确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=auto

延迟消息的发送

@Autowired
private RabbitMQConfig rabbitMQConfig;
@Autowired
private RabbitTemplate rabbitTemplate;

//发送延迟消息
EventMessage eventMessage = EventMessage.builder()
    .eventMessageType(EventMessageType.PRODUCT_ORDER_NEW.name())
    .accountNo(loginUser.getAccountNo())
    .bizId(orderOutTradeNo)
    .build();

rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(),rabbitMQConfig.getOrderCloseDelayRoutingKey(),eventMessage);

关单消费者处理

@Component
@Slf4j
@RabbitListener(queuesToDeclare = {@Queue("order.close.queue")})
public class ProductOrderMQListener {
 
    @Autowired
    private ProductOrderService productOrderService;
 
    @RabbitHandler
    public void productOrderHandler(EventMessage eventMessage, Message message, Channel channel){
        log.info("监听到消息ProductOrderMQListener messsage消息内容:{}",message);
        try{
            //关闭订单 业务逻辑
            productOrderService.closeProductOrder(eventMessage);
        }catch (Exception e){
            log.error("消费者失败:{}",eventMessage);
            throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
        }
        log.info("消费成功:{}",eventMessage);
    }
}

监听到延迟的关单消息后,根据实际业务进行查询订单、支付等确定是否要进行关单操作。

来源:
https://blog.csdn.net/wnn654321/article/details/122832362



相关推荐

几种 TCP 连接中出现 RST 的情况

现在是一个网络时代了。应该不少程序员在编程中需要考虑多机、局域网、广域网的各种问题。所以网络知识也是避免不了学习的。而且笔者一直觉得TCP/IP网络知识在一个程序员知识体系中必需占有一席之地的。在...

Redis连接使用报RDB error错误

该错误信息:Errorinexecution;nestedexceptionisio.lettuce.core.RedisCommandExecutionException:MISC...

lua 语法介绍与 NGINX lua 高级用法实战操作

一、概述lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。官网:https://www.lua.org/二、l...

Python教程——20.协程 - 2

异步编程asyncio.Future对象Task继承Future,Task对象内部中的await结果的处理基于Future对象来的在Future对象中会保存当前执行的这个协程任务的状态,如果当...

“我的足迹”、“浏览历史”,Redis如何快速记录与展示?

咱们在网上“买买买”、“逛逛逛”的时候,总会留下各种各样的“足迹”。无论是电商APP里你最近浏览过的商品,视频网站上你刚刚看过的剧集,还是新闻客户端里你点开过的文章……这些“历史记录”,有时候还真挺有...

你手机上的“消息推送”,Redis可能参与其中

手机上那些时不时就“叮咚”一下的消息推送,确实是咱们数字生活里不可或缺的一部分。这篇咱们就来聊聊,Redis这位“消息灵通人士”,是如何在这场“信息接力赛”中大显身手,确保那些重要的、有趣的通知,能够...

短视频APP的“附近的人”,Redis如何快速匹配?

刷短视频,除了看各种搞笑段子、才艺展示,有时候是不是也想看看“同城”或者“附近”的人都在发些啥有意思的内容?或者,平台也会时不时地给你推荐一些“附近正在直播”的主播,让你感觉一下子拉近了和这个虚拟世界...

微信朋友圈的点赞、评论,Redis在背后默默付出

微信朋友圈,这片小小的“自留地”,承载了我们多少喜怒哀乐、生活点滴啊!一张精心修饰的照片,一段随感而发的文字,发出去之后,最期待的是什么?那必须是屏幕下方不断冒出来的小红心和一条条真诚(或者商业互吹)...

网站登录老是掉线?Redis帮你记住你是谁!

有没有过这样的糟心体验?你好不容易登录了一个网站,刚看了两篇帖子,或者购物车里刚加了几件宝贝,结果一刷新页面,或者稍微离开了一会儿,回来就发现——“哎?我怎么又退出了?!”又得重新输入用户名、密码、...

你常用的APP,哪些地方可能用到了Redis?(猜想与分析)

咱们现在的生活,简直是离不开各种各样的手机APP了!从早上睁眼刷新闻,到中午点外卖,再到晚上刷短视频、玩游戏,一天到头,指尖在屏幕上就没停过。这些APP为了让我们用得爽、用得顺心,背后可是使出了浑身解...

Redis是啥?为啥程序员天天挂嘴边?小白也能看懂!

这Redis到底是何方神圣?为啥那些天天在电脑前敲代码的程序员小哥哥小姐姐们,老是把它挂在嘴边,好像离了它地球都不转了似的?别担心,咱们今天不说那些听了就头大的代码和术语,就用大白话,保证你听完一拍大...

面试官:请你说说Redis为什么这么快?

1)Redis是基于内存的存储数据库,绝大部分的命令处理只是纯粹的内存操作,内存的读写速度非常快。2)Redis是单进程线程的服务(实际上一个正在运行的RedisServer肯定不止一个线程,但只有...

有了强大的关系型数据库,为什么还需要Redis?

在数字世界的浩瀚海洋中,关系型数据库,例如我们熟知的MySQL、PostgreSQL或Oracle,无疑是那些承载着核心业务数据、坚如磐石的“国家图书馆”或“银行金库”。它们以严谨的结构、强大的事务处...

Java 中间件数据可靠性串讲:从 MQ 、MySQL、Redis 不丢失的保障之道

引言在现代分布式系统中,中间件扮演着至关重要的角色,它们是构建高可用、高性能、高可扩展应用架构的基石。消息队列(MQ)、数据库(如MySQL)、缓存(如Redis)等是其中最具代表性的组件。然而,...

运维部署方式之——虚机部署

标准化使用作業系统:LinuxCentOS7自动化方式通过Ansible系统初始化playbook来管理。目的系统初始化工作是一个简单、繁复的工作,从云网得到的虚拟主机只是一个基础的系统环境,...

取消回复欢迎 发表评论: