RabbitMQ--确认(confirm)模式、回退(return)模式、ACK、重试机制

spring:
  rabbitmq:
    host:    
    port:  
    username:  
    password:   
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual #开启手动确认
        retry:
          enabled: true #开启重试 
          max-attempts: 5 #重试次数 默认3
          initial-interval: 2000 #间隔时间 默认1秒
    publisher-confirm-type: correlated #确定消息发送到交换机
    publisher-returns: true  #确认消息已发送到队列
自定义confirm和 returnedMessage
package com.test.mq;

import com.test.mq.production.TestProduction;
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;

/**
 * @author csy
 * @description
 * @date 2022/4/15
 */
@Component
@Slf4j
public class CustomConfirmAndReturnCallback  implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @PostConstruct
    public void init() {
        /*  mandatory:交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
            true:RabbitMQ会调用Basic.Return命令将消息返回给生产者
            false:RabbitMQ会把消息直接丢弃*/
        rabbitTemplate.setMandatory(true);
        //指定 ConfirmCallback
        rabbitTemplate.setConfirmCallback(this);
        //指定 ReturnsCallback
        rabbitTemplate.setReturnsCallback(this);
    }
    // 消息只要被rabbit broker接收到就会执行confirmCallback
    // 被broker执行只能保证消息到达服务器,并不能保证一定被投递到目标queue里
    @Override
    public void confirm(CorrelationData correlationData, boolean isSendSuccess, String error) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (isSendSuccess) {
            log.info("发送消息到交换器成功, id:{}", id);
        } else {
            log.error("发送消息到交换器失败, id:{}, cause:{}", id, error);
        }
    }
    // confirm 模式只能保证消息达到broker 不能保证消息准确投递到目标queuez中
    // 有些业务场景下,需要保证消息一定投递到目标queue中,此时需要用到return退回模式
    // 如果未能达到目前queue中将调用returnCallback,可以记录下详细投递数据,定期巡检或者纠错

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        String exchange = returnedMessage.getExchange();
       /* 请注意!如果你使用了延迟队列插件,那么一定会调用该callback方法
        因为数据并没有提交上去,而是提交在交换器中,过期时间到了才提交上去,并非是bug!你可以用if进行判断交换机名称来捕捉该报错*/
        if(exchange.equals(TestProduction.EXCHANGE_TEST)){
            return;
        }
        log.info("消息被退回>>。msg:{}, replyCode:{}. replyText:{}, exchange:{}, routingKey :{}",
                returnedMessage.getMessage(), returnedMessage.getReplyCode(), returnedMessage.getReplyText(),
                exchange , returnedMessage.getRoutingKey());
    }
}

生产者
package com.test.mq.production;

import com.jiuyi.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;


@Component
@Slf4j
public class TestProduction{

    @Resource
    private RabbitTemplate rabbitTemplate;


    public static final String QUEUE_TEST = "queue_test";
    public static final String EXCHANGE_TEST = "exchange_test";


    @Bean(EXCHANGE_TEST)
    public FanoutExchange EXCHANGE_TEST() {
        return ExchangeBuilder.fanoutExchange(EXCHANGE_TEST).delayed().durable(true).build();
    }

    @Bean(QUEUE_TEST)
    public Queue QUEUE_TEST() {
        return new Queue(QUEUE_TEST, true);
    }

    @Bean
    public Binding BINDING_QUEUE_TEST(@Qualifier(QUEUE_TEST) Queue queue,
                                                  @Qualifier(EXCHANGE_TEST) FanoutExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange);
    }

    public void send(String message, Integer delay) {
        //msg id 唯一
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        log.info("生产开始时间:" + DateUtils.getNowDate());
        //发送消息时指定 header 延迟时间
        rabbitTemplate.convertAndSend(EXCHANGE_TEST,
                "", message,
                message1 -> {
                    //设置消息持久化
                    message1.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    if (delay != null) {
                        message1.getMessageProperties().setDelay(delay*1000);
                    }

                    return message1;
                }, correlationData);
        log.info("生产结束时间:" + DateUtils.getNowDate());
    }
}

消费者
package com.test.mq.listener;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Objects;

/**
 * @author csy
 * @description
 * @date 2022/4/15
 */
@Slf4j
@Component
public class TestListener {

    /*
     *监听并且创建队列 (防止没有声明队列而报错)
     * */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = "queue_test", durable = "true", autoDelete = "false"),
                    exchange = @Exchange(value = "exchange_test",
                            type = ExchangeTypes.FANOUT, delayed = "true")
            )
    )
    @RabbitHandler
    public void process(Message msg, Channel channel) throws IOException {
        String id = (String)msg.getMessageProperties().getHeader("spring_returned_message_correlation");
        System.out.println("收到消息了,id:"+id);//可以通过redis,防止消息重复消费
        long deliveryTag = msg.getMessageProperties().getDeliveryTag();
        if (Objects.nonNull(msg.getBody())) {
            System.out.println(new String(msg.getBody()));
            System.out.println("收到消息了");
            int i = 1 / 0;//报错会重试5次
        }
        /**
         * 无异常就确认消息
         * basicAck(long deliveryTag, boolean multiple)
         * deliveryTag:取出来当前消息在队列中的的索引;
         * multiple:为true的话就是批量确认,会确认索引前的所有消息,一般设置为false
         */
        channel.basicAck(deliveryTag, false);

        /**
         * 有异常就绝收消息 --不建议使用
         * basicNack(long deliveryTag, boolean multiple, boolean requeue)
         * requeue:true为将消息重返当前消息队列,还可以重新发送给消费者; ****一直异常会死循环,****
         *         false:将消息丢弃
         */
        //channel.basicNack(deliveryTag, false, true);

    }


}

你可能感兴趣的:(java,rabbitmq,spring,boot,spring,cloud,中间件)