RabbitMQ 入门系列6 -- 持久化与死信队列

1 RabbitMQ 的持久化

1.1 场景导入

我在之前的博客提到过,当消费者消息接收到一半的时候挂掉时,可以使用消息接收确认机制进行补救。那在更加极端的场景下,RabbitMQ 挂掉了,又该怎么办呢?

在默认情况下,队列与消息都是非持久化的,想要将队列与消息进行保存,可以使用 RabbitMQ 的持久化机制。

1.2 理论原理

  1. RabbitMQ 所谓的持久化,就是将数据写在磁盘上
  2. 消息持久化分为三个部分,他们分别是:交换机持久化,队列持久化,消息持久化
  3. 如果交换机和队列都是持久化的,那么绑定也是持久化的,只要他们两个有一个是非持久化的,那么绑定也是非持久化的
  4. 队列与交换机的是否持久化在刚创建的时候就已经确定且不能更改,修改队列与交换机的是否持久化标志唯有删除信息并重建一个新的

1.3 队列持久化

如果队列不设置持久化 ,那么 RabbitMQ 服务重启后,相关的队列数据将会丢失。由于消息是存储在队列中的,所以队列中的消息也会丢失。

// 队列持久化代码
// 参数1 name :队列名  
// 参数2 durable :是否持久化  
// 参数3 exclusive :仅创建者可以使用的私有队列,断开后自动删除  
// 参数4 autoDelete : 当所有消费客户端连接断开后,是否自动删除队列  
new Queue(name, durable, exclusive, autoDelete); 

1.4 交换机持久化

如果交换器不设置持久化,那么 RabbitMQ 服务重启后,相关的交换器数据将会丢失,不过消息不会丢失,只是 RabbitMQ 生产者不能正常发送消息。

// 交换机持久化代码
// 参数1 name :交互器名  
// 参数2 durable :是否持久化  
// 参数3 autoDelete :当所有消费客户端连接断开后,是否自动删除队列  
new TopicExchange(name, durable, autoDelete)

1.5 消息持久化

我们都知道,队列的持久化只能保证队列本身的数据不会丢失,如果需要保证消息不会丢失则需要消息本身被持久化。

至于消息持久化的设置,由于我们通常使用 rabbitTemplate.convertAndSend(exchange, routeKey, message); 来发送消息,而这种方法已经默认消息是持久化的,所以一般我们不需要设置。

2 死信队列

2.1 简介

死信队列,又称 dead-letter-exchange(DLX)。当一条消息在一个队列中变成死信后,它会被重新发布到一个交换机中,这个交换机就是 DLX。

2.2 消息变成死信的几种情况

  1. 消息被拒绝(reject ,nack),并且 requeue = false(不再重新投递)
  2. 消息 TTL 过期
  3. 队列超过最长长度

2.3 实战

配置类

package com.example.consumer.config;

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

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 直连型交换机
 * @author 30309
 *
 */
@Configuration
public class DirectRabbitConfig {

	//队列,名称为DirectQueue
	//同时将DirectQueue绑定到死信队列交换机上
    @Bean
    Queue DirectQueue() {
    	Map<String, Object> args = new HashMap<>(2);
    	//交换机标识符
        args.put("x-dead-letter-exchange", "DeadExchange");
        //绑定键标识符
        args.put("x-dead-letter-routing-key", "DeadRoutingKey");
        Queue queue = new Queue("DirectQueue", true, false, false, args);
        return queue;
    }
 
    //直连型交换机,名称为DirectExchange
    @Bean
    DirectExchange DirectExchange() {
        return new DirectExchange("DirectExchange");
    }
 
    //将队列和交换机绑定, 并设置用于匹配键:DirectRouting
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(DirectQueue()).to(DirectExchange()).with("DirectRouting");
    }
    
    //创建死信队列
    @Bean
    Queue DeadQueue() {
        return new Queue("DeadQueue", true);
    }
    
    //创建死信交换机
    @Bean
    DirectExchange DeadExchange() {
        return new DirectExchange("DeadExchange");
    }
    
    //死信队列与死信交换机绑定
    @Bean
    Binding bindingDead() {
        return BindingBuilder.bind(DeadQueue()).to(DeadExchange()).with("DeadRoutingKey");
    }
    
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { 
    	RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 消息发送失败返回到队列中, 配置文件需要配置 publisher-returns: true
        rabbitTemplate.setMandatory(true);
        
        return rabbitTemplate;
    }
   
    
}

生产者:

package com.example.provider.controller;

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.RestController;
 
/**
 * 生产者
 * @author 30309
 *
 */
@RestController
public class SendMessageController{
 
    @Autowired
    RabbitTemplate rabbitTemplate; 
 
    @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
    	
        //将消息携带绑定键值DirectRouting发送到交换机DirectExchange
    	rabbitTemplate.convertAndSend("DirectExchange", "DirectRouting", "Hello World");
    	
        return "ok";
    }
 
}

消费者1,连接普通队列,我们设置让它直接拒绝消息

package com.example.consumer.receiver;

import java.io.IOException;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import com.rabbitmq.client.Channel;

/**
 * 消费者1
 * @author 30309
 *
 */
@Component
public class DirectReceiver1 {

	@RabbitListener(queues = "DirectQueue")
	@RabbitHandler
    public void process(String str,Channel channel, Message message) {
        System.out.println("DirectReceiver1消费者收到消息: " + str );
        
		try {
			channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

消费者2,我们让它连接死信队列,可以发现它收到被消费者1拒绝的消息

package com.example.consumer.receiver;

import java.io.IOException;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import com.rabbitmq.client.Channel;

/**
 * 消费者2
 * @author 30309
 *
 */
@Component
public class DirectReceiver2 {

	@RabbitListener(queues = "DeadQueue")
	@RabbitHandler
    public void process(String str,Channel channel, Message message) {
        System.out.println("DirectReceiver2消费者收到消息: " + str );
        
		try {
			channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

测试结果如下
在这里插入图片描述
结果正常

2.4 总结

  1. DLX 其实也是正常的交换机,能在任何队列上被指定
  2. 当队列中有死信时,RabbitMQ 会自动将消息重新发布到设置的交换机上去,进而被路由到另一个队列

参考:springboot实现rabbitmq的持久化和消息确认
RabbitMQ的死信队列详解

发布了113 篇原创文章 · 获赞 206 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geffin/article/details/102686403