SpringBoot整合RabbitMQ消息发布确认(二)

前面有一篇文章测试一下发布确认
RabbitMQ消息发布确认
现在有一个问题:
在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。 于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢?特别是在这样比较极端的情况, RabbitMQ 集群不可用的时候,无法投递的消息该如何处理呢?

确认机制方案

在这里插入图片描述

代码架构图

在这里插入图片描述

配置文件

在配置文件当中需要添加
spring.rabbitmq.publisher-confirm-type=correlated

  • NONE
    禁用发布确认模式,是默认值
  • CORRELATED
    发布消息成功到交换器后会触发回调方法
  • SIMPLE
    经测试有两种效果:
    其一效果和 CORRELATED 值一样会触发回调方法;
    其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker
#配置MQ连接信息
spring.rabbitmq.addresses=192.168.235.128
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
spring.rabbitmq.publisher-confirm-type=correlated

配置类代码

package com.xiang.springboot_rabbitmq.config;

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

/**
 * 发布确认
 */
@Configuration
public class ConfirmConfig {

    //交换机
    public static final String CONFIRM_EXCHANGE = "confirm_exchange";
    //队列
    public static final String CONFIRM_QUEUE = "confirm_queue";
    //routingKey
    public static final String CONFIRM_ROUTING_KEY = "key1";

    //声明交换机
    @Bean
    public DirectExchange confirmExchange(){
        return new DirectExchange(CONFIRM_EXCHANGE);
    }

    //声明队列
    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE).build();
    }

    //confirmQueue绑定ConfirmExchange
    @Bean
    public Binding confirmQueueBindingConfirmExchange(Queue confirmQueue,DirectExchange confirmExchange){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
    }
}

生产者代码

import com.xiang.springboot_rabbitmq.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试发布确认
 */
@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProduceController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public void sendMessage(@RequestParam String message){
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE,ConfirmConfig.CONFIRM_ROUTING_KEY,message);
        log.info("发出消息:{}",message);
    }
}

消费者代码



import com.rabbitmq.client.Channel;
import com.xiang.springboot_rabbitmq.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;


/**
 * 监听 发布确认
 */
@Slf4j
@Component
public class ConfirmConsumer {

    @RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE)
    public void receiveConfirmMessage(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("当前时间{},收到消息:{}",new Date().toString(),msg);
    }

}

启动测试

查看日志输出
在这里插入图片描述
正常情况下 成功发出并消费。

但是如果MQ宕机,消息发不出去?应该把消息保存下来,确保消息不会丢失。
生产者应该感知交换机有没有收到消息。增加一个确认回调接口

在这里插入图片描述

回调接口

package com.xiang.springboot_rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
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;

@Component
@Slf4j
public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback {
    //注入到RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initialize(){
        rabbitTemplate.setConfirmCallback(this);
    }

    /**
     * 交换机确认回调方法
     * 1、发消息 交换机成功接收
     * 2、发消息 交换机接收失败
     * @param correlationData 回调消息的id以及相关信息
     * @param ack 是否收到消息
     * @param cause 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack){ //成功接收
            log.info("成功接收id为:{}",id);
        }else {
            log.info("接收消息失败id为:{},原因:{}",id,cause);
        }
    }
}

要想自己的回调接口生效,需要在项目配置文件中添加

spring.rabbitmq.publisher-confirm-type=correlated

生产者改造

/**
 * 测试发布确认
 */
@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProduceController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public void sendMessage(@RequestParam String message){
        CorrelationData correlationData = new CorrelationData("1");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE,ConfirmConfig.CONFIRM_ROUTING_KEY,message,correlationData);
        log.info("发出消息:{}",message);
    }
}

启动测试一下
执行了回调函数
在这里插入图片描述
为了测试收不到消息,我们将生产者发送给一个不存在的交换机进行测试,模拟MQ宕机。


import com.xiang.springboot_rabbitmq.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试发布确认
 */
@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProduceController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public void sendMessage(@RequestParam String message){
        CorrelationData correlationData = new CorrelationData("1");
        //测试发送给一个不存在的交换机
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE +"1",ConfirmConfig.CONFIRM_ROUTING_KEY,message,correlationData);
        log.info("发出消息:{}",message);
    }
}

在这里插入图片描述

在这里插入图片描述
现在交换机不存在解决了,那么如果交换机正常收到消息,但是队列不存在,还会执行回调函数嘛?
测试一下


/**
 * 测试发布确认
 */
@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProduceController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public void sendMessage(@RequestParam String message){
        CorrelationData correlationData = new CorrelationData("1");
        //测试发送给一个不存在的队列
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE ,ConfirmConfig.CONFIRM_ROUTING_KEY +"1",message,correlationData);
        log.info("发出消息:{}",message);
    }
}

在这里插入图片描述
交换机收到消息,执行回调,但是队列没有收到消息,导致消费者没有接收到消息。。
以看到,发送了两条消息,第一条消息的 RoutingKey 为 “key1”,第二条消息的 RoutingKey 为
RoutingKey 与队列的 BindingKey 不一致,也没有其它队列能接收这个消息,所有消息被直接丢弃了。

回退消息

Mandatory 参数

在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息, 如
果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。
那么如何让无法被路由的消息帮我想办法处理一下?最起码通知我一声,我好自己处理啊。通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。

配置文件中添加

spring.rabbitmq.publisher-returns=true

回调接口


import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
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;

@Component
@Slf4j
public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
    //注入到RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initialize(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }

    /**
     * 交换机确认回调方法
     * 1、发消息 交换机成功接收
     * 2、发消息 交换机接收失败
     * @param correlationData 回调消息的id以及相关信息
     * @param ack 是否收到消息
     * @param cause 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack){ //成功接收
            log.info("成功接收id为:{}",id);
        }else {
            log.info("接收消息失败id为:{},原因:{}",id,cause);
        }
    }

    /**
     * 消息回退
     * 只有消息不可达目的地的时候,才进行回退。
     * @param returned
     */
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        log.info("交换机:{},消息被退回:{},退回原因:{}",
                returned.getExchange(),returned.getMessage().getBody(),returned.getReplyText());
    }
}



生产者代码

/**
 * 测试发布确认
 */
@RestController
@Slf4j
@RequestMapping("/confirm")
public class ProduceController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMessage")
    public void sendMessage(@RequestParam String message){
        CorrelationData correlationData = new CorrelationData("1");
        //测试发送给一个不存在的队列
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE ,ConfirmConfig.CONFIRM_ROUTING_KEY +"1",message,correlationData);
        log.info("发出消息:{}",message);
    }
}

启动测试
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值