Springboot结合rabbitmq实现的死信队列

 更新时间:2023年09月05日 09:16:04   作者:西安未央  
为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,本文主要介绍了Springboot结合rabbitmq实现的死信队列,具有一定的参考价值,感兴趣的可以了解一下

概述

RabbitMQ是流行的开源消息队列系统,使用erlang语言开发。为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常时,将消息投入死信队列中。但由于对死信队列的概念及配置不熟悉,导致曾一度陷入百度的汪洋大海,无法自拔,很多文章都看起来可行,但是实际上却并不能帮我解决实际问题。最终,在官网文档中找到了我想要的答案,通过官网文档的学习,才发现对于死信队列存在一些误解,导致配置死信队列之路困难重重。

详细

一、运行效果

image.png

image.png

二、实现过程

①、先创建一个Springboot项目。然后在pom文件中添加  spring-boot-starter-amqp  和  spring-boot-starter-web 的依赖,接下来创建一个Config类,这里是关键:

package com.zyf.rabbitmqdeadletterdemo.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
    public static final String BUSINESS_EXCHANGE_NAME = "rabbitmq.dead.letter.business.exchange";
    public static final String BUSINESS_QUEUEA_NAME = "rabbitmq.dead.letter.business.queueA";
    public static final String BUSINESS_QUEUEB_NAME = "rabbitmq.dead.letter.business.queueB";
    public static final String DEAD_LETTER_EXCHANGE = "rabbitmq.dead.letter.deadletter.exchange";
    public static final String DEAD_LETTER_QUEUEA_ROUTING_KEY = "rabbitmq.dead.letter.deadletter.queueA.routingkey";
    public static final String DEAD_LETTER_QUEUEB_ROUTING_KEY = "rabbitmq.dead.letter.deadletter.queueB.routingkey";
    public static final String DEAD_LETTER_QUEUEA_NAME = "rabbitmq.dead.letter.deadletter.queueA";
    public static final String DEAD_LETTER_QUEUEB_NAME = "rabbitmq.dead.letter.deadletter.queueB";
    // 声明业务Exchange
    @Bean("businessExchange")
    public FanoutExchange businessExchange(){
        return new FanoutExchange(BUSINESS_EXCHANGE_NAME);
    }
    // 声明死信Exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }
    // 声明业务队列A
    @Bean("businessQueueA")
    public Queue businessQueueA(){
        Map<String, Object> args = new HashMap<>(2);
//       x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
//       x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
        return QueueBuilder.durable(BUSINESS_QUEUEA_NAME).withArguments(args).build();
    }
    // 声明业务队列B
    @Bean("businessQueueB")
    public Queue businessQueueB(){
        Map<String, Object> args = new HashMap<>(2);
//       x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
//       x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEB_ROUTING_KEY);
        return QueueBuilder.durable(BUSINESS_QUEUEB_NAME).withArguments(args).build();
    }
    // 声明死信队列A
    @Bean("deadLetterQueueA")
    public Queue deadLetterQueueA(){
        return new Queue(DEAD_LETTER_QUEUEA_NAME);
    }
    // 声明死信队列B
    @Bean("deadLetterQueueB")
    public Queue deadLetterQueueB(){
        return new Queue(DEAD_LETTER_QUEUEB_NAME);
    }
    // 声明业务队列A绑定关系
    @Bean
    public Binding businessBindingA(@Qualifier("businessQueueA") Queue queue,
                                    @Qualifier("businessExchange") FanoutExchange exchange){
        return BindingBuilder.bind(queue).to(exchange);
    }
    // 声明业务队列B绑定关系
    @Bean
    public Binding businessBindingB(@Qualifier("businessQueueB") Queue queue,
                                    @Qualifier("businessExchange") FanoutExchange exchange){
        return BindingBuilder.bind(queue).to(exchange);
    }
    // 声明死信队列A绑定关系
    @Bean
    public Binding deadLetterBindingA(@Qualifier("deadLetterQueueA") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);
    }
    // 声明死信队列B绑定关系
    @Bean
    public Binding deadLetterBindingB(@Qualifier("deadLetterQueueB") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);
    }
}

②、接下来,是业务队列的消费代码:

@Slf4j@Componentpublic class BusinessMessageReceiver {    @RabbitListener(queues = BUSINESS_QUEUEA_NAME)
    public void receiveA(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("收到业务消息A:{}", msg);        boolean ack = true;
        Exception exception = null;        try {            if (msg.contains("deadletter")){                throw new RuntimeException("dead letter exception");
            }
        } catch (Exception e){
            ack = false;
            exception = e;
        }        if (!ack){
            log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        } else {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }    @RabbitListener(queues = BUSINESS_QUEUEB_NAME)
    public void receiveB(Message message, Channel channel) throws IOException {
        System.out.println("收到业务消息B:" + new String(message.getBody()));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

③、然后配置死信队列的消费者:

@Componentpublic class DeadLetterMessageReceiver {    @RabbitListener(queues = DEAD_LETTER_QUEUEA_NAME)
    public void receiveA(Message message, Channel channel) throws IOException {
        System.out.println("收到死信消息A:" + new String(message.getBody()));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }    @RabbitListener(queues = DEAD_LETTER_QUEUEB_NAME)
    public void receiveB(Message message, Channel channel) throws IOException {
        System.out.println("收到死信消息B:" + new String(message.getBody()));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

④、为了方便测试,写一个简单的消息生产者,并通过controller层来生产消息。

@Componentpublic class BusinessMessageSender {    @Autowired
    private RabbitTemplate rabbitTemplate;    public void sendMsg(String msg){
        rabbitTemplate.convertSendAndReceive(BUSINESS_EXCHANGE_NAME, "", msg);
    }
}
@RequestMapping("rabbitmq")@RestControllerpublic class RabbitMQMsgController {    @Autowired
    private BusinessMessageSender sender;    @RequestMapping("sendmsg")
    public void sendMsg(String msg){
        sender.sendMsg(msg);
    }
}

三、项目结构图

image.png

四、补充总结

死信队列其实并没有什么神秘的地方,不过是绑定在死信交换机上的普通队列,而死信交换机也只是一个普通的交换机,不过是用来专门处理死信的交换机。

总结一下死信消息的生命周期:

  • 业务消息被投入业务队列
  • 消费者消费业务队列的消息,由于处理过程中发生异常,于是进行了nck或者reject操作
  • 被nck或reject的消息由RabbitMQ投递到死信交换机中
  • 死信交换机将消息投入相应的死信队列
  • 死信队列的消费者消费死信消息

死信消息是RabbitMQ为我们做的一层保证,其实我们也可以不使用死信队列,而是在消息消费异常时,将消息主动投递到另一个交换机中,当你明白了这些之后,这些Exchange和Queue想怎样配合就能怎么配合。比如从死信队列拉取消息,然后发送邮件、短信、钉钉通知来通知开发人员关注。或者将消息重新投递到一个队列然后设置过期时间,来进行延时消费。

到此这篇关于Springboot结合rabbitmq实现的死信队列的文章就介绍到这了,更多相关Springboot rabbitmq死信队列内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现图片上传到服务器并把上传的图片读取出来

    Java实现图片上传到服务器并把上传的图片读取出来

    在各大网站上都可以实现上传头像功能,可以选择自己喜欢的图片做头像,从本地上传,今天小编给大家分享Java实现图片上传到服务器并把上传的图片读取出来,需要的朋友参考下
    2017-02-02
  • 详解SpringBoot 调用外部接口的三种方式

    详解SpringBoot 调用外部接口的三种方式

    SpringBoot不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程,这篇文章主要介绍了SpringBoot 调用外部接口的三种方式,需要的朋友可以参考下
    2023-04-04
  • Java精品项目瑞吉外卖之登陆的完善与退出功能篇

    Java精品项目瑞吉外卖之登陆的完善与退出功能篇

    这篇文章主要为大家详细介绍了java精品项目-瑞吉外卖订餐系统,此项目过大,分为多章独立讲解,本篇内容为新增菜品和分页查询功能的实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • java稀疏数组的示例代码

    java稀疏数组的示例代码

    这篇文章主要介绍了java稀疏数组,稀疏数组,记录一共有几行几列,有多少个不同值,把具有不同值的元素和行里了及值记录在一个小规模的数组中,从而缩小程序的规模,对java稀疏数组相关知识感兴趣的朋友一起看看吧
    2022-07-07
  • 使用IDEA如何将本地代码上传到GitCode

    使用IDEA如何将本地代码上传到GitCode

    文章主要介绍了使用GitCode进行代码仓库管理的步骤,包括注册、创建仓库、IDEA创建本地仓库、设置远程仓库、配置账号密码和进行代码推送等操作,文章强调使用CSDN账号同步注册,并使用个人访问令牌进行操作,最后完成代码的推送
    2026-04-04
  • 详解如何在SpringBoot中自定义参数解析器

    详解如何在SpringBoot中自定义参数解析器

    这篇文章主要为大家详细介绍了如何在SpringBoot中实现自定义参数解析器,文中的示例代码讲解详细,对我们学习或工作有一定帮助,需要的可以参考一下
    2022-05-05
  • Eureka源码阅读之环境搭建及工程结构

    Eureka源码阅读之环境搭建及工程结构

    这篇文章主要为大家介绍了Eureka源码阅读之环境搭建的工程结构及调试需知详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
    2022-10-10
  • Spring Security放行的接口Knife4j静态资源的问题小结

    Spring Security放行的接口Knife4j静态资源的问题小结

    这篇文章主要介绍了Spring Security使用Knife4j静态资源的问题小结,项目中使用 Spring Security 做身份认证和授权,使用 Knife4j 做接口调试,需要 Spring Security 放行的接口记录在 RequestMatcherConstant 类中,感兴趣的朋友跟随小编一起看看吧
    2024-02-02
  • SpringBoot封装响应数据实现过程详解

    SpringBoot封装响应数据实现过程详解

    这篇文章主要介绍了SpringBoot封装响应数据实现过程,SpringBoot响应数据封装是指在SpringBoot应用程序中,将返回的数据进行封装,以便于前端页面或其他客户端使用,感兴趣想要详细了解可以参考下文
    2023-05-05
  • Java杂谈之合格程序员一定要会阅读别人的源码

    Java杂谈之合格程序员一定要会阅读别人的源码

    阅读别人的代码作为开发人员是一件经常要做的事情。一个是学习新的编程语言的时候通过阅读别人的代码是一个最好的学习方法,另外是积累编程经验
    2021-09-09

最新评论