rabbitMq的死信实现

死信是什么?

死信,在官网中对应的单词为“Dead Letter”,可以看出翻译确实非常的简单粗暴。那么死信是个什么东西呢?
“死信”是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:
消息被否定确认,使用 channel.basicNack 或 channel.basicReject ,并且此时requeue 属性被设置为false。
消息在队列的存活时间超过设置的TTL时间。
消息队列的消息数量已经超过最大队列长度。
那么该消息将成为“死信”。
“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。

如何配置死信

大致步奏
1 配置业务队列,绑定到业务交换机上
2 为业务队列配置死信交换机和路由key
3 为死信交换机配置死信队列
注意,并不是直接声明一个公共的死信队列,然后所以死信消息就自己跑到死信队列里去了。而是为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后为每个业务队列分配一个单独的路由key。

有了死信交换机和路由key后,接下来,就像配置业务队列一样,配置死信队列,然后绑定在死信交换机上。也就是说,死信队列并不是什么特殊的队列,只不过是绑定在死信交换机上的队列。死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型【Direct、Fanout、Topic】。一般来说,会为每个业务队列分配一个独有的路由key,并对应的配置一个死信队列进行监听,也就是说,一般会为每个重要的业务队列配置一个死信队列。

实现

首先搭建好springBoot项目,配置好rabbitMq
在这里插入图片描述
编写发送回调类RabbitConfirmCallback,RabbitConfirmReturnCallBack

package com.example.config;

import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

public class RabbitConfirmCallback implements RabbitTemplate.ConfirmCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        System.out.println("=======ConfirmCallback=========");
        System.out.println("correlationData = " + correlationData);
        System.out.println("ack = " + ack);
        System.out.println("cause = " + cause);
        System.out.println("=======ConfirmCallback=========");
    }
}

package com.example.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

public class RabbitConfirmReturnCallBack implements RabbitTemplate.ReturnCallback {
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("--------------ReturnCallback----------------");
        System.out.println("message = " + message);
        System.out.println("replyCode = " + replyCode);
        System.out.println("replyText = " + replyText);
        System.out.println("exchange = " + exchange);
        System.out.println("routingKey = " + routingKey);
        System.out.println("--------------ReturnCallback----------------");
    }
}

编写配置类 RabbitDeadLetterConfig

package com.example.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitDeadLetterConfig {
    /**
     * 死信队列1.
     **/
    private String deadQueueA = "dead_Letter_QueueA";

    /**
     * 死信队列2.
     **/
    private String deadQueueB = "dead_Letter_QueueB";

    /**
     * 死信交换机.
     **/
    private String deadExChange = "dead_direct_ExChange";

    /**
     * 业务队列A.
     **/
    private String businessQueueA = "business_QueueA";

    /**
     * 业务队列B.
     **/
    private String businessQueueB = "business_QueueB";

    /**
     * 业务交换机.
     **/
    private String businessExchange = "business_Topic_Exchange";

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    private void initRabbitTemplate() {
        //设置发送回调
        rabbitTemplate.setConfirmCallback(new RabbitConfirmCallback());
        rabbitTemplate.setReturnCallback(new RabbitConfirmReturnCallBack());
    }

    /**
     * 声明死信队列1.
     *
     * @return
     */
    @Bean
    public Queue deadLetterQueueA() {
        return new Queue(deadQueueA);
    }

    /**
     * 声明死信队列2.
     *
     * @return
     */
    @Bean
    public Queue deadLetterQueueB() {
        return new Queue(deadQueueB);
    }

    /**
     * 声明死信交换机.
     *
     * @return
     */
    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange(deadExChange);
    }

    /**
     * 绑定死信队列A 至 死信交换机.
     *
     * @return
     */
    @Bean
    public Binding bindingDeadExchangeA(Queue deadLetterQueueA, DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(deadLetterQueueA).to(deadLetterExchange).with("update");
    }

    /**
     * 绑定死信队列B 至 死信交换机.
     *
     * @return
     */
    @Bean
    public Binding bindingDeadExchangeB(Queue deadLetterQueueB, DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(deadLetterQueueB).to(deadLetterExchange).with("add");
    }

    /**
     * 声明业务队列A.
     *
     * @return
     */
    @Bean
    public Queue businessQueueA() {
        Map<String, Object> map = new HashMap<>(2);
        //       x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        map.put("x-dead-letter-exchange", deadExChange);
        //       x-dead-letter-routing-key  这里声明当前队列的死信路由key
        map.put("x-dead-letter-routing-key", "update");
        return QueueBuilder.durable(businessQueueA).withArguments(map).build();
    }

    /**
     * 声明业务队列B.
     *
     * @return
     */
    @Bean
    public Queue businessQueueB() {
        Map<String, Object> map = new HashMap<>(2);
        //       x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        map.put("x-dead-letter-exchange", deadExChange);
        //       x-dead-letter-routing-key  这里声明当前队列的死信路由key
        map.put("x-dead-letter-routing-key", "add");
        return QueueBuilder.durable(businessQueueB).withArguments(map).build();
    }

    /**
     * 声明业务交换机.
     *
     * @return
     */
    @Bean
    public TopicExchange businessTopicExchange() {
        return new TopicExchange(businessExchange);
    }

    /**
     * 绑定业务队列A.
     *
     * @return
     */
    @Bean
    public Binding bindingBusinessQueueA(Queue businessQueueA, TopicExchange businessTopicExchange) {
        return BindingBuilder.bind(businessQueueA).to(businessTopicExchange).with("topicA");
    }

    /**
     * 绑定业务队列B.
     *
     * @return
     */
    @Bean
    public Binding bindingBusinessQueueB(Queue businessQueueB, TopicExchange businessTopicExchange) {
        return BindingBuilder.bind(businessQueueB).to(businessTopicExchange).with("topicA.#");
    }
}

编写消息发送类

package com.example.controller;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ProducerMessage {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessageA() {
        String message = "deadTest";
        rabbitTemplate.convertAndSend("business_Topic_Exchange", "topicA", message);
    }

    public void sendMessageB() {
        String message = "deadLetter";
        rabbitTemplate.convertAndSend("business_Topic_Exchange", "topicA.AaBb", message);
    }
}

编写消费者

package com.example.service;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class ConsumerTest {

    /**
     * 监听业务队列A.
     */
    @RabbitListener(queues = "business_QueueA")
    public void readBusinessA(Message message, Channel channel) throws IOException {
        Boolean ack = true;
        String str = new String(message.getBody());
        if (str.contains("deadTest")) {
            ack = false;
        }

        if (!ack) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            System.out.println("我是监控业务AAAAA,消费失败");
        } else {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("我是监控业务AAAAA,消费成功,消息内容是" + str);
        }
    }

    /**
     * 监听业务队列B.
     */
    @RabbitListener(queues = "business_QueueB")
    public void readBusinessB(Message message, Channel channel) throws IOException {
        String str = new String(message.getBody());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        System.out.println("我是监控业务BBBBB,消费成功,消息内容是" + str);
    }

    /**
     * 监听死信队列A.
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = "dead_Letter_QueueA")
    public void readDeadMessageA(Message message, Channel channel) throws IOException {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        System.out.println("死信消费者AAAAA:" + new String(message.getBody()));
    }

    /**
     * 监听死信队列B.
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = "dead_Letter_QueueB")
    public void readDeadMessageB(Message message, Channel channel) throws IOException {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        System.out.println("死信消费者BBBBBB:" + new String(message.getBody()));
    }
}

编写访问类

package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(value = "rabbitMq")
public class RabbitMqController {

    @Autowired
    private ProducerMessage producerMessage;

    @RequestMapping(value = "sendTestA")
    @ResponseBody
    public String sendTestA() {
        producerMessage.sendMessageA();
        return "success";
    }

    @RequestMapping(value = "sendTestB")
    @ResponseBody
    public String sendTestB() {
        producerMessage.sendMessageB();
        return "success";
    }
}

测试

启动项目,查看rabbit界面发现 队列多出了DlX DLK
在这里插入图片描述
在这里插入图片描述
访问sendTestA方法得出
在这里插入图片描述
访问sendTestB方法得出
在这里插入图片描述
总结
死信队列其实并没有什么神秘的地方,不过是在普通队列上绑定死信交换机,而死信交换机也只是一个普通的交换机,不过是用来专门处理死信的交换机。

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

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

访问sendTestA 得出结果流程
sendTestA ()触发sendMessageA方法,发出routingKey为topicA
在这里插入图片描述
我们发现routingKey为topicA 时 两个队列都满足条件,在这里插入图片描述
那么将触发监控这两个队列的消费者
在这里插入图片描述
当进去消费者B时顺利被消费,控制台打印
在这里插入图片描述
当进去消费者A时
在这里插入图片描述
由于发送的消息,包含deadTest 消费失败 ,手动拒绝确认
控制台打印
在这里插入图片描述
当消费者A 消费失败 触发 .basicNack方法时
死信队列被触发,由于 business_QueueA 队列绑定死信队列
在这里插入图片描述
发出routingKey为update 触发监控死信update的发方法
在这里插入图片描述
然后被消费控制台打印
在这里插入图片描述
总体结果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值