RabbitMQ发布确认高级版

本文介绍了如何在SpringBoot应用中通过配置RabbitMQ的publisher-confirm-type属性实现消息的可靠投递,包括使用CORRELATED模式触发回调、SIMPLE模式的确认处理以及启用消息回退机制以确保消息在接收失败时返回给生产者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

在生产环境中由于一些不明原因,导致 RabbitMQ 重启,在 RabbitMQ 重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢?
在这里插入图片描述在这里插入图片描述

2.添加配置信息

在application.properties文件中添加如下配置,交换机开启消息确认模式

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

3. 配置类

package com.hong.springboot.rabbitmq.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;

/**
 * @Description: 发布确认高级版配置类
 * @Author: hong
 * @Date: 2024-03-05 20:52
 * @Version: 1.0
 **/
@Configuration
public class ConfirmConfig {
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
    public static final String CONFIRM_ROUTING_KEY = "key1";

    //声明业务 Exchange
    @Bean("confirmExchange")
    public DirectExchange confirmExchange() {
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }

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

    // 声明确认队列绑定关系
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(CONFIRM_ROUTING_KEY);
    }
}

4.生产者

package com.hong.springboot.rabbitmq.controller;

import com.hong.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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Description: 发布确认高级版生产者
 * @Author: hong
 * @Date: 2024-03-05 20:58
 * @Version: 1.0
 **/
@Slf4j
@RequestMapping("/confirm/")
@RestController
public class ConfirmProducerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //http://localhost:8080/confirm/sendMsg/Hi,JAVA小生不才
    @GetMapping("sendMsg/{message}")
    public void sendMsg(@PathVariable String message) {
        log.info("当前时间:{},发送信息给队列:{}",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) , message);
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME, ConfirmConfig.CONFIRM_ROUTING_KEY, message);
    }
}

5.消费者

package com.hong.springboot.rabbitmq.consumer;

import com.hong.springboot.rabbitmq.config.ConfirmConfig;
import com.hong.springboot.rabbitmq.config.DelayedQueueConfig;
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.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Description: 发布确认高级版消费者
 * @Author: hong
 * @Date: 2024-03-05 21:05
 * @Version: 1.0
 **/
@Slf4j
@Component
public class ConfirmConsumer {
    @RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
    public void receiveConfirmMessage(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到信息{}",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) , msg);
    }
}

正常情况下,发送http://localhost:8080/confirm/sendMsg/Hi,JAVA小生不才
在这里插入图片描述

6.回调接口

package com.hong.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;

/**
 * @Description: 发布确认高级版消息生产者的回调接口
 * @Author: hong
 * @Date: 2024-03-09 21:58
 * @Version: 1.0
 **/
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback{
    @Autowired
    private RabbitTemplate rabbitTemplate;

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

    /**
     * 交换机不管是否收到消息的一个回调方法
     * 1.收到消息
     * correlationData   保存回调消息的id及相关信息
     * b true   交换机收到消息
     * s null
     * 2.未收到消息
     * correlationData   保存回调消息的id及相关信息
     * b false   交换机未收到消息
     * s 失败的原因
     * @param correlationData  消息相关数据
     * @param b           交换机是否收到消息
     * @param s             没收到消息的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (b) {
            log.info("交换机已经收到id为:{}的消息", id);
        } else {
            log.info("交换机还未收到id为:{}消息,原因:{}", id, s);
        }
    }
}

修改ConfirmProducerController中sendMsg方法
交换机改个名字模拟交换机收不到消息

    @GetMapping("sendMsg/{message}")
    public void sendMsg(@PathVariable String message) {
        CorrelationData correlationData = new CorrelationData("1");
        log.info("当前时间:{},发送信息给队列:{}",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) , message);
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME+"123", ConfirmConfig.CONFIRM_ROUTING_KEY, message,correlationData);
    }

在这里插入图片描述
将routingKey改个名字模拟队列收不到消息

    @GetMapping("sendMsg/{message}")
    public void sendMsg(@PathVariable String message) {
        CorrelationData correlationData1 = new CorrelationData("1");
        log.info("当前时间:{},发送信息给队列:{}",
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) , message+"----"+ConfirmConfig.CONFIRM_ROUTING_KEY);
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME, ConfirmConfig.CONFIRM_ROUTING_KEY,
                message+"----"+ConfirmConfig.CONFIRM_ROUTING_KEY,correlationData1);

        CorrelationData correlationData2 = new CorrelationData("2");
        log.info("当前时间:{},发送信息给队列:{}",
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) , message+"----"+ConfirmConfig.CONFIRM_ROUTING_KEY+"abc");
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME, ConfirmConfig.CONFIRM_ROUTING_KEY+"abc",
                message+"----"+ConfirmConfig.CONFIRM_ROUTING_KEY+"abc",correlationData2);
    }

在这里插入图片描述

7.回退消息

从以上模拟场景可以看出,在仅开启生产者确认机制,交换机接收到消息后,会直接给生产者发送确认消息,但若发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃的。因此我们借用mandatory参数在当消息传递过程中不可达目的地时将消息返回给生产者。

7.1.开启消息回退机制

配置文件中添加如下配置

#开启消息回退机制
spring.rabbitmq.publisher-returns=true

7.2. 添加消息回退回调

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }
    
    /**
     * 当消息传递过程中不可达目的地时将消息返回给生产者
     * 只有不可达目的地时才回调
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("消息:{},被交换机 {} 退回,原因:{},路由key:{},code:{}",
                new String(returnedMessage.getMessage().getBody()), returnedMessage.getExchange(),
                returnedMessage.getReplyText(), returnedMessage.getRoutingKey(), returnedMessage.getReplyCode());
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值