RabbitMQ消息确认以及死信队列

RabbitMQ消息确认以及死信队列

  • 回调函数
  • 消息确认机制
  • 死信队列
    • 什么是死信队列
    • 定义队列

回调函数

回调函数是指当消息发送到交换机或队列是的回调通知。

  1. 修改yml配置文件
spring:
  profiles:
    active: dev
  # rabbitmq 配置
  rabbitmq:
    host: 10.10.11.21
    port: 5672
    username: guest
    password: guest
    virtual-host: / # 设置虚拟主机,不设置则使用默认host
    # 消息确认配置项
    # 确认消息已发送到交换机: Exchange
    publisher-confirm-type: correlated
    # 确认消息已发送到队列: Queue
    publisher-returns: true
  1. 配置相关的消息确认回调函数

/**
 * 配置回调
 */
@Configuration
public class RabbitConfig {

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);
		// 没有找到交换机的回调,没有找到队列也会调用这个回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("ConfirmCallback:     "+"相关数据:"+correlationData);
                System.out.println("ConfirmCallback:     "+"确认情况:"+ack);
                System.out.println("ConfirmCallback:     "+"原因:"+cause);
            }
        });
		// 没有找到队列的回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("ReturnCallback:     "+"消息:"+message);
                System.out.println("ReturnCallback:     "+"回应码:"+replyCode);
                System.out.println("ReturnCallback:     "+"回应信息:"+replyText);
                System.out.println("ReturnCallback:     "+"交换机:"+exchange);
                System.out.println("ReturnCallback:     "+"路由键:"+routingKey);
            }
        });

        return rabbitTemplate;
    }
}

上面写了两个回调函数,一个叫 ConfirmCallback ,一个叫 RetrunCallback;
推送消息存在四种情况:
①消息推送到server,但是在server里找不到交换机
②消息推送到server,找到交换机了,但是没找到队列
③消息推送到sever,交换机和队列啥都没找到
④消息推送成功
经过测试得出结论:
①这种情况触发的是 ConfirmCallback 回调函数。
②这种情况触发的是 ConfirmCallback和RetrunCallback两个回调函数。
③这种情况触发的是 ConfirmCallback 回调函数。
④这种情况触发的是 ConfirmCallback 回调函数。

消息确认机制

和生产者的消息确认机制不同,因为消息接收本来就是在监听消息,符合条件的消息就会消费下来。
所以,消息接收的确认机制主要存在三种模式:

自动确认, 这也是默认的消息确认情况。 AcknowledgeMode.NONE
RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递。
所以这种情况如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
一般这种情况我们都是使用try catch捕捉异常后,打印日志用于追踪数据,这样找出对应数据再做后续处理。

根据情况确认, 这个不做介绍
手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。
消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。
basic.ack用于肯定确认
basic.nack用于否定确认(注意:这是AMQP 0-9-1的RabbitMQ扩展)
basic.reject用于否定确认,但与basic.nack相比有一个限制:一次只能拒绝单条消息

消费者端以上的3个方法都表示消息已经被正确投递,但是basic.ack表示消息已经被正确处理。
而basic.nack,basic.reject表示没有被正确处理。

channel.basicReject(deliveryTag, true); 拒绝消费当前消息,如果第二参数传入true,就是将数据重新丢回队列里,那么下次还会消费这消息。设置false,就是告诉服务器,我已经知道这条消息数据了,因为一些原因拒绝它,而且服务器也把这个消息丢掉就行。 下次不想再消费这条消息了。

使用拒绝后重新入列这个确认模式要谨慎,因为一般都是出现异常的时候,catch异常再拒绝入列,选择是否重入列。

但是如果使用不当会导致一些每次都被你重入列的消息一直消费-入列-消费-入列这样循环,会导致消息积压.

channel.basicNack(deliveryTag, false, true);
第一个参数依然是当前消息到的数据的唯一id;
第二个参数是指是否针对多条消息;如果是true,也就是说一次性针对当前通道的消息的tagID小于当前这条消息的,都拒绝确认。
第三个参数是指是否重新入列,也就是指不确认的消息是否重新丢回到队列里面去。

同样使用不确认后重新入列这个确认模式要谨慎,因为这里也可能因为考虑不周出现消息一直被重新丢回去的情况,导致积压。

  1. 配置yml配置文件
spring:
  profiles:
    active: dev
  # rabbitmq 配置
  rabbitmq:
    host: 10.10.11.21
    port: 5672
    username: guest
    password: guest
    virtual-host: / # 设置虚拟主机,不设置则使用默认host
    # 消息确认配置项
    # 确认消息已发送到交换机: Exchange
    publisher-confirm-type: correlated
    # 确认消息已发送到队列: Queue
    publisher-returns: true
    listener:
      simple:
        acknowledge-mode: manual # 采用手动应答, auto:自定应答
        default-requeue-rejected: false

// 消费者监听

@Configuration
public class DirectReceiver {

    @RabbitListener(queues = "test-direct-queue") //监听队列的名称
    public void processDirect(Message message, Channel channel) throws IOException {
        // 采用手动应答模式,手动确认应答更加安全稳定
        System.out.println("direct监听消息===第一个====:"+ new String(message.getBody()));
//        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true); // 确认消费
       // channel.basicNack(deliveryTag, false, true);

        /**
         * 设置不消费某条消息
         * //第一个参数依然是当前消息到的数据的唯一id;
         *         //第二个参数是指是否针对多条消息;如果是true,也就是说一次性针对当前通道的消息的tagID小于当前这条消息的,都拒绝确认。
         *         //第三个参数是指是否重新入列,也就是指不确认的消息是否重新丢回到队列里面去
         */
//        channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);

        /**
         * 拒绝消费
         * 如果第二参数传入true,就是将数据重新丢回队列里,那么下次还会消费这消息。设置false,
         * 就是告诉服务器,我已经知道这条消息数据了,因为一些原因拒绝它,而且服务器也把这个消息丢掉就行。 下次不想再消费这条消息了
          */
        channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);

    }
}

死信队列

什么是死信队列

死信”是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:

  • 消息被否定确认,使用 channel.basicNack 或 channel.basicReject ,并且此时requeue 属性被设置为false。
  • 消息在队列的存活时间超过设置的TTL时间。
  • 消息队列的消息数量已经超过最大队列长度。

那么该消息将成为“死信”,“死信”消息会被RabbitMQ进行特殊处理。如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。

定义队列

配置的方法就是在声明队列的时候,添加参数 x-dead-letter-exchangex-dead-letter-routing-key,其实就是在消费失败时,将消息使用该 exchange 及 routing 发送至指定队列

/**
 * @Description:
 * @ClassName TopicRabbitConfig
 * @date: 2021.08.23 09:52
 * @Author: zhanghang
 */
@Configuration
public class TopicRabbitConfig {
	// 正常的topic 队列
	public static final String TEST_TOPIC_EXCHANGE_NAME = "test_topic_exchange_name";
	public static final String TEST_TOPIC_QUEUE_NAME = "test_topic_queue_name";
	public static final String TEST_TOPIC_ROUTING_NAME = "test_topic_routing_name";

	// topic 死信队列
	public static final String TEST_DEAD_TOPIC_EXCHANGE_NAME = "test_dead_topic_exchange_name";
	public static final String TEST_DEAD_TOPIC_QUEUE_NAME = "test_dead_topic_queue_name";
	public static final String TEST_DEAD_TOPIC_ROUTING_NAME = "test_dead_topic_routing_name";

	// 正常的队列
	@Bean
	public Queue createTopicQueue(){
		Map<String, Object> arguments = new HashMap<>(2);
		// 绑定死信交换机
		arguments.put("x-dead-letter-exchange", TEST_DEAD_TOPIC_EXCHANGE_NAME);
		// 绑定死信的路由key
		arguments.put("x-dead-letter-routing-key", TEST_DEAD_TOPIC_ROUTING_NAME);
		// 绑定死信队列的交换机和路由
		return new Queue(TEST_TOPIC_QUEUE_NAME,true,false,false,arguments);
	}

	@Bean
	public TopicExchange createTopicExchange(){
		// 参数说明:
		// name:    交换机名称
		// durable: 是否持久化
		// autoDelete: 是否自动删除
		return new TopicExchange(TEST_TOPIC_EXCHANGE_NAME,true,false);
	}

	@Bean
	public Binding createTopicBinding(){
		return BindingBuilder.bind(createTopicQueue()).to(createTopicExchange()).with(TEST_TOPIC_ROUTING_NAME);
	}

	// 死信队列
	@Bean
	public Queue createDeadTopicQueue(){
		return new Queue(TEST_DEAD_TOPIC_QUEUE_NAME,true,false,false);
	}

	@Bean
	public TopicExchange createDeadTopicExchange(){
		// 参数说明:
		// name:    交换机名称
		// durable: 是否持久化
		// autoDelete: 是否自动删除
		return new TopicExchange(TEST_DEAD_TOPIC_EXCHANGE_NAME,true,false);
	}

	@Bean
	public Binding createDeadTopicBinding(){
		return BindingBuilder.bind(createDeadTopicQueue()).to(createDeadTopicExchange()).with(TEST_DEAD_TOPIC_ROUTING_NAME);
	}

}

发送消息

	@GetMapping("/topic/sendMsg")
	public String topicSendMsg(){
		String msg = "hello World";
		rabbitTemplate.convertAndSend("test_topic_exchange_name","test_topic_routing_name",msg);
		return "ok~";
	}

监听消息

@Component
public class TestTopicListener {

	/**
	 * description: 正常消费的队列
	 * date: 2021年-08月-23日 10:30
	 * author: zhanghang
	 * 
	 * @param msg
 * @param channel
	 * @return void
	 */ 
	@RabbitListener(queues = "test_topic_queue_name")
	public void receiver(Message msg, Channel channel, @Headers Map<String,Object> headers) throws IOException, InterruptedException {
		try {
			//打印数据
			String message = new String(msg.getBody(), StandardCharsets.UTF_8);
			System.out.println("receiver消费消息{}"+message);
			System.out.println("headers{}"+headers);
//		channel.basicReject(msg.getMessageProperties().getDeliveryTag(), false);
			int i = 1/0; // 主动抛出异常
			channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);

		}catch (Exception e){
			System.out.println("receiver发生异常,拒绝重新入队{}");
			channel.basicReject(msg.getMessageProperties().getDeliveryTag(),false);
		}
	}


	// 监听死信队列
	@RabbitListener(queues = "test_dead_topic_queue_name")
	public void receiverDead(Message msg, Channel channel, @Headers Map<String,Object> headers) throws IOException, InterruptedException {
		//打印数据
		String message = new String(msg.getBody(), StandardCharsets.UTF_8);
		System.out.println("receiverDead消费消息{}"+message);
		System.out.println("headers{}"+headers);
		channel.basicAck(msg.getMessageProperties().getDeliveryTag(),true);
	}
}

经过测试发现。当正常的消费者拒绝消息就会进入到死信队列。

你可能感兴趣的:(中间件,RabbitMQ)