RabbitMQ进阶--TTL,死信队列,持久化,磁盘监控

一.消息的TTL机制

RabbitMQ中的TTL(Time-To-Live,存活时间)是指消息或队列的过期时间。TTL机制允许你指定消息在未被消费前可以存活的时间长度,超过这个时间后,消息将被视为“死亡”,并从队列中移除。这种机制对于控制消息的有效性和资源管理非常有用。

防止积压消息:当系统因故障或负载过高无法及时处理消息时,旧的消息可能会堆积,影响新消息的处理。设置TTL可以帮助清理这些不再需要的消息。
消息有效期控制:某些场景下,消息只在特定时间段内有效。比如促销信息、验证码等,超出一定时间后就失去意义,此时就可以利用TTL自动清理这些消息。
资源管理:有助于避免队列无限增长占用过多内存或磁盘空间,尤其是在发布/订阅模式下,确保消费者不会因为过期消息而受到不必要的负担。

需要注意的是,即使设置了TTL,消息也不会立即被删除,而是等到它们到达队列头部时才会检查是否过期。此外,也可以配置死信交换机(Dead Letter Exchange),以便当消息过期时将其路由到另一个交换机进行进一步处理。

目前有两种方法可以设置。

  • 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
  • 第二种方法是对消息进行单独设置,每条消息TTL可以不同。

如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列, 消费者将无法再收到该消息。

第一种:创建一个TTL队列

使用配置类的方式创建:

使用声明队列时参数x-message-ttl,表示过期时间,单位为ms

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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

@Configuration
public class TTLConfig {
    //声明一个direct的交换机
    @Bean
    public DirectExchange TTLDirectExchange(){
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    //声明一个TTL队列
    @Bean
    public Queue TTLDirectQueue(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("x-message-ttl",5000);
        return new Queue("ttl_queue",true,false,false,map);
    }
    //绑定交换机和队列
    @Bean
    public Binding TTLDirectBinding(){
        return BindingBuilder.bind(TTLDirectQueue()).to(TTLDirectExchange()).with("ttl");
    }
}

 

业务方法:简单的向这个队列发送一条信息,队列带有TTL属性,5s后自动删除

    /**
     *
     * @param userID 用户ID
     * @return void
     * @Description 模拟用户下单
     **/
    //发送消息到TTL队列中
    public void sendTTLMsg(Long userID){
        String uuid = UUID.randomUUID().toString();
        System.out.println("用户:"+userID+",订单是:"+uuid);
        //发送信息
        rabbitTemplate.convertAndSend("ttl_direct_exchange","ttl",uuid);
    }

 

测试方法:

@Test
void testTTLQueue(){
    directService.sendTTLMsg(1L);
}

 

测试结果:

 5s之后:

 第二种:设置消息的TTL

设置消息的TTL只需要一个普通的队列就好了,过期时间是由单条消息决定的

声明一个普通队列

    //声明一个direct的交换机
    @Bean
    public DirectExchange TTLDirectExchange(){
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    //声明一个普通的队列
    @Bean
    public Queue TTLDirectQueueMsg(){
        return new Queue("ttl_msg_queue",true);
    }
    //绑定其与交换机的关系
    @Bean
    public Binding TTLBindingMsg(){
        return BindingBuilder.bind(TTLDirectQueueMsg()).to(TTLDirectExchange()).with("ttlMsg");
    }

 发送消息业务类,设置消息过期时间:

/**
     *
     * @param userID 用户ID
     * @return void
     * @Description 模拟用户下单
     **/
    //发送消息到TTL队列中
    public void sendTTLMsgTwo(Long userID){
        String uuid = UUID.randomUUID().toString();
        System.out.println("用户:"+userID+",订单是:"+uuid);
        //设置消息TTL
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //这里就是字符串
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };
        //发送信息
        rabbitTemplate.convertAndSend("ttl_direct_exchange","ttlMsg",uuid,messagePostProcessor);
    }

 

测试方法:

@Test
    void testTTLMsgQueue(){
        directService.sendTTLMsgTwo(250L);
    }

 

结果截图:

5s之后:

 二.死信队列

DLX,全称为Dead-Letter-Exchange , 可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下的原因:

    • 消息被拒绝
    • 消息过期
    • 队列达到最大长度

 DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange 指定交换机即可。

 如果想要将信息投递到死信队列,有三种方式,一种时消息超过TTL时间,另一种是当前队列的存储已满,会出队的信息到死信队列,还有一种是当前队列拒绝接收消息

TTL过期

 声明一个死信交换机,也是一个普通的交换机,作为死信的用法:

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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DeadQueueConfig {

    //声明一个交换机,专门用于死信队列
    @Bean
    public DirectExchange deadExchange(){
        return new DirectExchange("dead_direct_exchange",true,false);
    }

    //声明死信队列
    @Bean
    public Queue deadQueue(){
        return new Queue("dead_queue",true);
    }

    //绑定队列和交换机
    @Bean
    public Binding deadBinding(){
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");
    }
}

 

修改TTL队列,需要指定死信交换机以及死信路由key:

//声明一个TTL队列
    @Bean
    public Queue TTLDirectQueue(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("x-message-ttl",5000);
        //指定消息过期之后的死信队列
        map.put("x-dead-letter-exchange","dead_direct_exchange");//消息过期转存到的交换机,死信交换机也是一个普通的交换机
        map.put("x-dead-letter-routing-key","dead");// 转发时候需要指定路由key;direct模式
        return new Queue("ttl_queue",true,false,false,map);
    }

 

注意,如果此队列已经存在,需要删除后在运行,已有的队列无法修改参数,只能重新创造。

测试方法:就是向TTL队列发送信息,等待过期后,转发到死信队列:

@Test
    void testTTLQueue(){
        directService.sendTTLMsg(1L);
    }

 

测试结果:

5s之后:

 队列达到最大值

 模拟队列达到最大信息接收值,可以将队列容量先改小,然后让其溢出

这里我们设置最大值5条

//声明一个普通的队列
    @Bean
    public Queue TTLDirectQueueMsg(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("x-max-length",5);//指定队列最大接收值
        map.put("x-dead-letter-exchange","dead_direct_exchange");//消息过期转存到的交换机,死信交换机也是一个普通的交换机
        map.put("x-dead-letter-routing-key","dead");// 转发时候需要指定路由key;direct模式
        return new Queue("ttl_msg_queue",true);
    }

 

测试方法:一次发送10条信息:

 @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void testMaxLengthQueue(){
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("ttl_direct_exchange","ttlMsg", UUID.randomUUID().toString());
            System.out.println("发送成功:"+(i+1)+",-----");
        }
    }

结果截图:

 队列详情:

 如上,果然队列只装下了5条消息,而其它的消息都在死信队列

三.RabbitMQ的持久化机制

持久化就把信息写入到磁盘的过程。

  • 非持久消息:

是指当内存不够用的时候,会把消息和数据转移到磁盘,但是重启以后非持久化队列消息就丢失。

  • RabbitMQ的持久化队列分为:

1:队列持久化
2:消息持久化
3:交换机持久化
不论是持久化的消息还是非持久化的消息都可以写入到磁盘中,只不过非持久的是等内存不足的情况下才会被写入到磁盘中。

 队列的持久化是定义队列时的durable参数来实现的,Durable为true时,队列才会持久化。

// 参数1:名字  
// 参数2:是否持久化,
// 参数3:独du占的queue, 
// 参数4:不使用时是否自动删除,
// 参数5:其他参数
channel.queueDeclare(queueName,true,false,false,null);

 

 其中参数2:设置为true,就代表的是持久化的含义。即durable=true。持久化的队列在web控制台中有一个D 的标记

 非持久化队列

 消息持久化是通过消息的属性deliveryMode来设置是否持久化,在发送消息时通过basicPublish的参数传入。

// 参数1:交换机的名字
// 参数2:队列或者路由key
// 参数3:是否进行消息持久化
// 参数4:发送消息的内容
channel.basicPublish(exchangeName, routingKey1, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

 

和队列一样,交换机也需要在定义的时候设置持久化的标识,否则在rabbit-server服务重启以后将丢失。

// 参数1:交换机的名字
// 参数2:交换机的类型,topic/direct/fanout/headers
// 参数3:是否持久化
channel.exchangeDeclare(exchangeName,exchangeType,true);

 

四.RabbitMQ的内存监控

RabbitMQ的内存警告

 当内存使用超过配置的阈值或者磁盘空间剩余空间对于配置的阈值时,RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息,以此避免服务器的崩溃,客户端与服务端的心态检测机制也会失效。

RabbitMQ的内存控制

命令的方式

rabbitmqctl set_vm_memory_high_watermark <fraction>
rabbitmqctl set_vm_memory_high_watermark absolute 50MB

fraction/value 为内存阈值。默认情况是:0.4/2GB,代表的含义是:当RabbitMQ的内存超过40%时,就会产生警告并且阻塞所有生产者的连接。通过此命令修改阈值在Broker重启以后将会失效,通过修改配置文件方式设置的阈值则不会随着重启而消失,但修改了配置文件一样要重启broker才会生效。

测试:

当我们没有修改相关的参数时:

 将分配给RabbitMQ的内存修改为50MB是,那么RabbitMQ,必会出现爆红和blocked,即队列阻塞,将不再接收消息:

 由于RabbitMQ运行就需要118MB,故而肯定已修改就必会让用户连接变为blocked:

 测试完成后还是需要修改回来的:

rabbitmqctl set_vm_memory_high_watermark 0.4

 

使用config文件配置

当前配置文件:/etc/rabbitmq/rabbitmq.conf

#默认
#vm_memory_high_watermark.relative = 0.4
# 使用relative相对值进行设置fraction,建议取值在04~0.7之间,不建议超过0.7.
vm_memory_high_watermark.relative = 0.6
# 使用absolute的绝对值的方式,但是是KB,MB,GB对应的命令如下
vm_memory_high_watermark.absolute = 2GB

RabbitMQ的内存换页

在某个Broker节点及内存阻塞生产者之前,它会尝试将队列中的消息换页到磁盘以释放内存空间,持久化和非持久化的消息都会写入磁盘中,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉。

默认情况下,内存到达的阈值是50%时就会换页处理。
也就是说,在默认情况下该内存的阈值是0.4的情况下,当内存超过0.4*0.5=0.2时,会进行换页动作。

比如有1000MB内存,当内存的使用率达到了400MB,已经达到了极限,但是因为配置的换页内存0.5,这个时候会在达到极限400mb之前,会把内存中的200MB进行转移到磁盘中。从而达到稳健的运行。

vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置小于1的值)

为什么设置小于1,以为你如果你设置为1的阈值。内存都已经达到了极限了。你在去换页意义不是很大了。

RabbitMQ的磁盘预警

当磁盘的剩余空间低于确定的阈值时,RabbitMQ同样会阻塞生产者,这样可以避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器崩溃。

默认情况下:磁盘预警为50MB的时候会进行预警。表示当前磁盘空间第50MB的时候会阻塞生产者并且停止内存消息换页到磁盘的过程。
这个阈值可以减小,但是不能完全的消除因磁盘耗尽而导致崩溃的可能性。比如在两次磁盘空间的检查空隙内,第一次检查是:60MB ,第二检查可能就是1MB,就会出现警告。

通过命令方式修改如下:

rabbitmqctl set_disk_free_limit  <disk_limit>
rabbitmqctl set_disk_free_limit memory_limit  <fraction>
disk_limit:固定单位 KB MB GB
fraction :是相对阈值,建议范围在:1.0~2.0之间。(相对于内存)

 

比如,我们将磁盘剩余空间小于100GB后就报警:

rabbitmqctl set_disk_free_limit 100GB

 

由于我的服务器最大就是40GB,肯定剩余空间没有100GB,故而肯定会报警的:

测试完了记得修改回来:

rabbitmqctl set_disk_free_limit 50MB

 

通过配置文件配置如下:

disk_free_limit.relative = 3.0
disk_free_limit.absolute = 50mb

 

 

------END------

 

posted @ 2025-03-29 23:16  回忆也交给时间  阅读(181)  评论(0)    收藏  举报