RabbitMq---高级发布确认

目录

一. 解决交换机消息丢失

二.解决队列消息丢失

 三.备份队列与告警队列


在生产者发送消息后,可能会发生消息丢失。

(1)交换机因某种原因一直收不到消息,就导致了消息丢失。

(2)交换机收到消息了,但队列因某种原因一直收不到消息,就导致了消息丢失。

一. 解决交换机消息丢失

< 1 > 配置文件

#确认消息已发送到交换机(Exchange)
spring.rabbitmq.publisher-confirm-type=correlated

< 2 > rabbitmq配置类 

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
 *   rabbitmq 发布高级确认
 *
 * */
@Configuration
public class ConfirmConfig {
    //队列
    public static final String CONFIRM_QUEUE = "CONFIRM_QUEUE";
    //交换机
    public static final String CONFIRM_EXCHANGE = "CONFIRM_EXCHANGE";
    //routingKey
    public static final String CONFIRM_ROUNTING_KEY = "CONFIRM_ROUNTING_KEY";
   


    //声明交换机
    @Bean
    public DirectExchange confirmEchange() {
              // 路由Key异常,走 消息退回
       return (DirectExchange) ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).durable(true).build();
    }

    //声明队列
    @Bean
    public Queue confirmQueue() {
        //创建队列
        return new Queue(CONFIRM_QUEUE);
    }


    //绑定队列
    @Bean
    public Binding confirmBindingQueue(@Qualifier("confirmQueue") Queue confirmQueue, @Qualifier("confirmEchange") DirectExchange confirmEchange) {
        return BindingBuilder.bind(confirmQueue).to(confirmEchange).with(CONFIRM_ROUNTING_KEY);
    }

< 3 > 告警配置类(交换机确认是否收到消息)

该类需要实现 RabbitTemplate.ConfirmCallback 接口,再实现 confirm 方法。

该方法可以通过ack判断交换机是否收到消息。

注意:因为该方法需要依赖 RabbitTemplate ,所以要用 @PostConstruct 这个注解注入。


import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;

/**
 * 回调接口  告警配置类
 **/
@Component
@Slf4j
public class CallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private static Logger logger = LoggerFactory.getLogger(CallBack.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //相当于 注入,在其他注入结束后才进行注入
    @PostConstruct
    private void init() {
        //注入
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 交换机回调方法(针对于交换机是否成功接收消息)
     * 1.发消息 交换机接收到了 回调
     * 1.1 correlationData 保存回调消息的id及相关信息
     * 1.2 交换机收到消息 ack=true
     * 1.3 call null
     * 2. 发消息 交换机失败了 回调
     * 2.1 correlationData 保存回调消息的id及相关信息
     * 2.2 交换机收到消息 ack=false
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";

        if (ack) {
            logger.info("交换机接受消息成功了,消息id为:{}", id);
        } else {

            Message message = correlationData.getReturnedMessage();

            logger.info("交换机接受失败了,消息id为:{},失败原因:{}", id, cause);
            logger.info("消息是:{}",new String(message.getBody(), StandardCharsets.UTF_8));
        }

    }
}

< 4 > 生产者

故意把交换机写错,来测试交换机异常

// 交换机异常
    @GetMapping("/sendMsgToBadExchange")
    public void sendMsgToBadExchange() {
        //消息id
        CorrelationData correlationData = new CorrelationData("1");

        MessageProperties messageProperties = new MessageProperties();
        Message message = new Message("高级发布确认-交换机异常".getBytes(), messageProperties);

        correlationData.setReturnedMessage( new Message("correlationData message".getBytes(), messageProperties));

        //发送消息
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE + "bad", ConfirmConfig.CONFIRM_ROUNTING_KEY,  message, correlationData);
        log.info("测试交换机异常,发送消息,消息的ID是:{}",correlationData);
    }

测试:

测试结果已告知,交换机未收到消息,并已说明原因。

二.解决队列消息丢失

< 1 > 配置文件

#确认消息已发送到队列(Queue) 回退消息
spring.rabbitmq.publisher-returns=true

< 2 > 告警类配置

在交换机告警类上接着写的。

该接口要实现 RabbitTemplate.ReturnCallback接口,再实现 returnedMessage方法。

由于该方法也依赖 RabbitTemplate ,所以要用 @PostConstruct这个注解注入。

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;

/**
 * 回调接口  告警配置类
 **/
@Component
@Slf4j
public class CallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private static Logger logger = LoggerFactory.getLogger(CallBack.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //相当于 注入,在其他注入结束后才进行注入
    @PostConstruct
    private void init() {
        //注入
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 交换机回调方法(针对于交换机是否成功接收消息)
     * 1.发消息 交换机接收到了 回调
     * 1.1 correlationData 保存回调消息的id及相关信息
     * 1.2 交换机收到消息 ack=true
     * 1.3 call null
     * 2. 发消息 交换机失败了 回调
     * 2.1 correlationData 保存回调消息的id及相关信息
     * 2.2 交换机收到消息 ack=false
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";

        if (ack) {
            logger.info("交换机接受消息成功了,消息id为:{}", id);
        } else {

            Message message = correlationData.getReturnedMessage();

            logger.info("交换机接受失败了,消息id为:{},失败原因:{}", id, cause);
            logger.info("消息是:{},消息id:{}",new String(message.getBody(), StandardCharsets.UTF_8),id);
        }

    }

    /**
     * 只有消息不可达到目的地时,返回给生产者
     *
     * 如果有备份交换机,则不执行
     *
     * @param message    消息
     * @param replayCode 失败码
     * @param replayText 失败原因
     * @param exchanges  交换机
     * @param routingKey 路由
     */
    @Override
    public void returnedMessage(Message message, int replayCode, String replayText, String exchanges, String routingKey) {
        System.out.println("队列接受失败了");
        System.out.println("消息:" + new String(message.getBody(), StandardCharsets.UTF_8) +"     消息id:"+message.getMessageProperties().getMessageId()+ ";消息码:" + replayCode + ";原因:" + replayText + ";交换机:" + exchanges + ";路由:" + routingKey);
    }
}

 < 3 > 生产者

故意把路由Key写错,演示队列接受失败。

   //路由Key异常
    @GetMapping("/sendMsgToBadRounting")
    public void sendMsgToBadRounting() {
        //消息id
        CorrelationData correlationData = new CorrelationData("123");

        MessageProperties messageProperties = new MessageProperties();
        //消息id
        messageProperties.setMessageId(correlationData.getId());
        Message message = new Message("高级发布确认-路由Key异常".getBytes(), messageProperties);
        //发送消息
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE, ConfirmConfig.CONFIRM_ROUNTING_KEY +"bad",  message, correlationData);
        log.info("测试路由Key异常,发送消息,消息Id是:{}",correlationData);
    }

 测试:

测试结果,交换机接受成功了,队列接受失败了,并告知失败原因。

 

 三.备份队列与告警队列

        什么是备份交换机呢?备份 交换机可以理解为 RabbitMQ 中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时, 就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由 备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑 定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都 进入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。

< 1 > 配置类

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
 *   rabbitmq 发布高级确认
 *
 * */
@Configuration
public class ConfirmConfig {
    //队列
    public static final String CONFIRM_QUEUE = "CONFIRM_QUEUE";
    //交换机
    public static final String CONFIRM_EXCHANGE = "CONFIRM_EXCHANGE";
    //routingKey
    public static final String CONFIRM_ROUNTING_KEY = "CONFIRM_ROUNTING_KEY";
    //备份交换机
    public static final String BACKUP_EXCHANGE = "BACKUP_EXCHANGE";
    //备份队列
    public static final String BACKUP_QUEUE = "BACKUP_QUEUE";
    //告警队列
    public static final String WARNING_QUEUE = "WARNING_QUEUE";


    //声明交换机
    //这里因为用的是之前的交换机,所以需要删除原先的交换机才能生效
    @Bean
    public DirectExchange confirmEchange() {
        // 路由Key异常,走 消息退回
    //   return (DirectExchange) ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).durable(true).build();

        //添加了备份交换机 不走 消息退回
        return (DirectExchange) ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).durable(true).withArgument("alternate-exchange", BACKUP_EXCHANGE).build();
    }

    //声明队列
    @Bean
    public Queue confirmQueue() {
        //创建队列
        return new Queue(CONFIRM_QUEUE);
    }


    //绑定队列
    @Bean
    public Binding confirmBindingQueue(@Qualifier("confirmQueue") Queue confirmQueue, @Qualifier("confirmEchange") DirectExchange confirmEchange) {
        return BindingBuilder.bind(confirmQueue).to(confirmEchange).with(CONFIRM_ROUNTING_KEY);
    }

//----------------------------------------------------------------------------------------------------------------------------------------------------------

    //声明备份交换机
    @Bean
    public FanoutExchange backupEchange() {
        return new FanoutExchange(BACKUP_EXCHANGE);
    }


    //声明备份队列
    @Bean
    public Queue backupQueue() {
        //创建队列
        return new Queue(BACKUP_QUEUE);
    }

    //绑定备份队列
    @Bean
    public Binding backupBindingQueue(@Qualifier("backupQueue") Queue backupQueue, @Qualifier("backupEchange") FanoutExchange backupEchange) {
        return BindingBuilder.bind(backupQueue).to(backupEchange);
    }

//----------------------------------------------------------------------------------------------------------------------------------------------------------

    //声明报警队列
    @Bean
    public Queue warningQueue() {
        //创建队列
        return new Queue(WARNING_QUEUE);
    }


    //绑定报警队列
    @Bean
    public Binding warningBindingQueue(@Qualifier("warningQueue") Queue warningQueue, @Qualifier("backupEchange") FanoutExchange backupEchange) {
        return BindingBuilder.bind(warningQueue).to(backupEchange);
    }


}

< 2 >  消费者(备份)

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 *  备份 消费者
 **/
@Slf4j
@Component
public class BackupConsume {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //接收消息
    @RabbitListener(queues = ConfirmConfig.BACKUP_QUEUE)
    public void receiveDelay(Message message) {
        String messageId = message.getMessageProperties().getMessageId();
        String s = new String(message.getBody());
        log.info("走的是备份交换机消息:{} ,消费ID为:{}" ,s,messageId);
        
    }
}

< 3 >  消费者(告警)

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
  告警消费者
 **/
@Slf4j
@Component
public class WarningConsume {
    //接收消息
    @RabbitListener(queues = ConfirmConfig.WARNING_QUEUE)
    public void receiveDelay(Message msg) {

        String message = new String(msg.getBody());
        log.info("报警发现不可路由的消息:{},消息id:" + message,msg.getMessageProperties().getMessageId());
    }
}

 < 4 > 生产者

备份消费者和告警消费者是在消息不可达的时候才会会消费。因此,故意写错路由Key。

 //路由Key异常
    @GetMapping("/sendMsgToBadRounting")
    public void sendMsgToBadRounting() {
        //消息id
        CorrelationData correlationData = new CorrelationData("123");

        MessageProperties messageProperties = new MessageProperties();
        //消息id
        messageProperties.setMessageId(correlationData.getId());
        Message message = new Message("高级发布确认-路由Key异常".getBytes(), messageProperties);
        //发送消息
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE, ConfirmConfig.CONFIRM_ROUNTING_KEY +"bad",  message, correlationData);
        log.info("测试路由Key异常,发送消息,消息Id是:{}",correlationData);
    }

测试:

测试结果,交换机接受正常,备份队列和告警队列都接受到了消息。

因为添加了备份交换机,消息退回就不执行了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值