方案一:spring提供的retry

配置文件:

server:
  port:8080
spring:
  rabbitmq:
    host: xxx.xxx.xxx.xxx
    port: 5672
    username: xxxx
    password: xxx
    publisher-confirm-type: correlated
    listener:
      simple:
        acknowledge-mode: manual #开启手动确认
        retry:
          enabled: true
          max-attempts: 5 #重试次数
          initial-interval: 5000  #重试间隔时间(单位毫秒)
          max-interval: 10000   #重试最大时间间隔(单位毫秒)
          stateless: true

错误的例子:

**开了失败重试后,在消失消费失败时,不能使用否定确认,会陷入死循环(重试机制就不生效了)**

消息消费失败了,消息回退到queue -> queue再次下发消息 -> 消费失败->消息回退到queue -> queue再次下发消息...

@RabbitListener(queues = HISTORY_QUEUE)
    public void receiveMsg1(Message message, Channel channel) {
        message.getMessageProperties();
        String msg = new String(message.getBody());
        LOGGER.info(HISTORY_QUEUE + " 接受到消息1:{}", msg);
        try {

            if ("test".equals(msg)) {
                throw new RuntimeException();
            }
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//手工确认,可接下一条

            LOGGER.info(HISTORY_QUEUE + " 消费成功1");
        } catch (Exception e) {
            LOGGER.info(HISTORY_QUEUE + " 消费失败1,param:{}", msg, e.getStackTrace());

            try {
//                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);//失败,则直接忽略此消息
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);//失败,将消息放回队列
//                channel.basicRecover(true); //消息将被重新排队,并可能被传递给不同的消费者。
            } catch (IOException ioException) {
                LOGGER.error(HISTORY_QUEUE + " 消息反馈失败1,param:{}", msg, ioException.getStackTrace());
            }
        }
    }

正确的用法:

**只确认消息消费正确的消息,消费异常的不处理;**

为什么用了try,还要手动抛异常?因为我想拿日志

    @RabbitListener(queues = HISTORY_QUEUE)
    public void receiveMsg1(Message message, Channel channel) {
        message.getMessageProperties();
        String msg = new String(message.getBody());
        LOGGER.info(HISTORY_QUEUE + " 接受到消息1:{}", msg);
        try {

            if ("test".equals(msg)) {
                throw new RuntimeException();
            }
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//手工确认,可接下一条

            LOGGER.info(HISTORY_QUEUE + " 消费成功1");
        } catch (Exception e) {
            LOGGER.info(HISTORY_QUEUE + " 消费失败1,param:{}", msg,  e.getMessage());
            throw new RuntimeException();
//            try {
////                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);//失败,则直接忽略此消息
//                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);//失败,将消息放回队列
//                channel.basicRecover(true); //消息将被重新排队,并可能被传递给不同的消费者。
//            } catch (IOException ioException) {
//                LOGGER.error(HISTORY_QUEUE + " 消息反馈失败1,param:{}", msg, ioException.getStackTrace());
//            }
        }
    }

方案2:死信交换机和消息头

需求:

发送消息推送广告,通过mq解耦;mq失败后重发三次,三次失败就丢弃消息;

配置:

server:
  port:8080
spring:
  rabbitmq:
    host: xxx.xxx.xxx.xxx
    port: 5672
    username: xxxx
    password: xxx
    publisher-confirm-type: correlated
    listener:
      simple:
        acknowledge-mode: manual #开启手动确认

 

package com.example.demo.config;

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

import java.util.HashMap;
import java.util.Map;

import static com.example.demo.config.HistoryDirectRabbitConfig.DEAD_EXCHANGE;
import static com.example.demo.config.HistoryDirectRabbitConfig.DEAD_ROUTING_KEY;

@Configuration
public class AgainMagSedConfig {
    public static final String AGAIN_QUEUE = "again.queue";
    public static final String AGAIN_EXCHANGE = "again.exchange";
    public static final String AGAIN_ROUTING_KEY = "again.routing.key";

    @Bean
    public Queue againQueue() {
        Map<String, Object> map = new HashMap<>();
        //正常队列设置死信交换机 参数 key 是固定值
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        //正常队列设置死信 routing-key 参数 key 是固定值
        map.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
        return new Queue(AGAIN_QUEUE, true, false, false, map);
    }

    @Bean
    public Exchange againExchange() {
        return new DirectExchange(AGAIN_EXCHANGE, true, false);
    }

    @Bean
    public Binding againRoutingKey(Queue againQueue, Exchange againExchange) {
        return BindingBuilder.bind(againQueue).to(againExchange).with(AGAIN_ROUTING_KEY).noargs();
    }
    
    public static final String DEAD_QUEUE = "dead.queue";
    public static final String DEAD_EXCHANGE = "dead.exchange";
    public static final String DEAD_ROUTING_KEY = "dead.routing.key";

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

    @Bean
    public Exchange deadExchange() {
        return new DirectExchange(DEAD_EXCHANGE, true, false);
    }

    @Bean
    public Binding deadRoutingKey(Queue deadQueue, Exchange deadExchange) {
        return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY).noargs();
    }

}

回调方法:

package com.example.demo.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

@Component
public class HistoryCallBack implements RabbitTemplate.ConfirmCallback {
    private static final Logger LOGGER = LoggerFactory.getLogger(HistoryCallBack.class);

    /**
     * 交换机不管是否收到消息的一个回调方法
     *
     * @param correlationData CorrelationData 回调的信息对象
     * @param ack             交换机是否成功接收消息
     * @param cause           交换机没收到消息的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack) {
            LOGGER.info("exchange receive msg, id:{}", id);
        } else {
            LOGGER.info("exchange not received msg, id:{},cause:{}", id, cause);
        }
    }

}

消息生产者:

package com.example.demo.controller;

import com.example.demo.config.HistoryCallBack;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

import static com.example.demo.config.AgainMagSedConfig.AGAIN_EXCHANGE;
import static com.example.demo.config.AgainMagSedConfig.AGAIN_ROUTING_KEY;

@RestController
@RequestMapping("/")
public class HelloController {


    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private HistoryCallBack historyCallBack;

    //依赖注入 rabbitTemplate 之后再设置它的回调对象
    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(historyCallBack);
    }

    @GetMapping("/hello/{msg}")
    public Boolean sendMq(@PathVariable("msg") String msg) {

        //定义发布回调的id
        CorrelationData correlationData = new CorrelationData(msg);
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) {
                message.getMessageProperties().setHeader("num", 1);
                return message;
            }
        };

        rabbitTemplate.convertAndSend(AGAIN_EXCHANGE, AGAIN_ROUTING_KEY, msg.getBytes(StandardCharsets.UTF_8), messagePostProcessor);
//        rabbitTemplate.convertAndSend(AGAIN_EXCHANGE, AGAIN_ROUTING_KEY, msg.getBytes(StandardCharsets.UTF_8), messagePostProcessor, correlationData);

        return true;
    }
}

消费者:

package com.example.demo.mqlistener;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
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;

import java.io.IOException;
import java.util.Map;

import static com.example.demo.config.AgainMagSedConfig.*;
import static com.example.demo.config.HistoryDirectRabbitConfig.DEAD_QUEUE;

@Component
public class AgainListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(AgainListener.class);
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 消费队列
     * @param channel
     * @param message
     * @throws IOException
     */
    @RabbitListener(queues = AGAIN_QUEUE)
    public void acceptAgainQueue(Channel channel, Message message) throws IOException {
        String s = new String(message.getBody());
        Map<String, Object> headers = message.getMessageProperties().getHeaders();
        LOGGER.info("队列:{},接受的消息是:{},请求头的num是:{}", AGAIN_QUEUE, s, headers.get("num"));
        if (s.equals("test")) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        } else {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

    /**
     * 死信队列
     * @param channel
     * @param message
     * @throws IOException
     */
    @RabbitListener(queues = DEAD_QUEUE)
    public void acceptDeadQueue(Channel channel, Message message) throws IOException {
        String s = new String(message.getBody());
        Map<String, Object> headers = message.getMessageProperties().getHeaders();
        LOGGER.info("死信队列:{},接受的消息是:{},请求头的num是:{}", DEAD_QUEUE, s, headers.get("num"));
        Integer num = (Integer) headers.get("num");
        final Integer num1 = ++num;
        if (num <= 3) {
            MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) {
                    message.getMessageProperties().setHeader("num", num1);
                    return message;
                }
            };
            rabbitTemplate.convertAndSend(AGAIN_EXCHANGE, AGAIN_ROUTING_KEY, s, messagePostProcessor);
        }

        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

效果:

 

posted on 2022-09-05 09:29  幂次方  阅读(3904)  评论(2编辑  收藏  举报