RabbitMQ消息的生存时间TTL

TTL(Time To Live)表示消息的生存时间,通常用于设置消息的延迟。TTL是一个时间值,以毫秒为单位,用于指定消息在被发送后多久将被认为是过期的。

在消息队列系统(例如RabbitMQ)中,通过设置消息的TTL,可以控制消息在队列中存活的时间。一旦消息的存活时间超过TTL,消息将被标记为过期并被丢弃或移动到死信队列(Dead Letter Queue)。

TTL可以用于实现延迟消息传递,其中消息在发送后不会立即被消费,而是等待一段时间后再被消费。这对于一些应用场景,比如实现任务调度或延迟处理等,非常有用。

在RabbitMQ中,可以通过设置消息属性 expiration 或通过队列的 x-message-ttl 参数来设置消息的TTL。

MQ环境测试准备

  • xExchange: 接收生成消息的交换机。

  • QA: 具有10s消息过期时间的队列,消息过期后通过yExchange交换机存入死信队列QD。

  • QB: 具有40s消息过期时间的队列,消息过期后通过yExchange交换机存入死信队列QD。

  • QC: 具有自定义消息过期时间的队列,消息过期后通过yExchange交换机存入死信队列QD。

  • QE: 有10s消息过期时间的队列,消息过期后丢弃消息。

  • yExchange: 接收过期消息的交换机。

  • QD: 死信队列。

代码实现

生产者 8080

定义队列和交换机

package com.zjw.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.util.HashMap;
import java.util.Map; /**
* TTL队列 配置文件类代码
* @author 朱俊伟
* @since 2022/09/06 22:01
*/
@Configuration
public class TtlQueueConfig { /**
* 普通交换机名称
*/
public static final String EXCHANGE_X = "X";
/**
* 死信交换机名称
*/
public static final String EXCHANGE_DEAD_LETTER_Y = "Y";
/**
*普通队列名称
*/
public static final String QUEUE_A = "QA";
public static final String QUEUE_B = "QB";
public static final String QUEUE_C = "QC";
public static final String QUEUE_E = "QE";
/**
* 死信队列名称
*/
public static final String DEAD_LETTER_QUEUE = "QD";
/**
* routing_key
*/
public static final String ROUTING_KEY_XA = "XA";
public static final String ROUTING_KEY_XB = "XB";
public static final String ROUTING_KEY_XC = "XC";
public static final String ROUTING_KEY_XE = "XE";
public static final String ROUTING_KEY_YD = "YD"; /**
* 直接交换机
* @return 交换机
*/
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(EXCHANGE_X);
} /**
* 死信交换机
* @return 交换机
*/
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(EXCHANGE_DEAD_LETTER_Y);
} /**
* 队列A
* @return 队列
*/
@Bean("queueA")
public Queue queueA(){
Map<String, Object> arguments = new HashMap<>();
//设置死信交换机
arguments.put("x-dead-letter-exchange", EXCHANGE_DEAD_LETTER_Y);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key", ROUTING_KEY_YD);
//设置TTL 单位是ms
arguments.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_A).withArguments(arguments).build();
} /**
* 队列B
* @return 队列
*/
@Bean("queueB")
public Queue queueB(){
Map<String, Object> arguments = new HashMap<>();
//设置死信交换机
arguments.put("x-dead-letter-exchange", EXCHANGE_DEAD_LETTER_Y);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key", ROUTING_KEY_YD);
//设置TTL 单位是ms
arguments.put("x-message-ttl", 40000);
return QueueBuilder.durable(QUEUE_B).withArguments(arguments).build();
} /**
* 队列C
* @return 队列
*/
@Bean("queueC")
public Queue queueC(){
Map<String, Object> arguments = new HashMap<>();
//设置死信交换机
arguments.put("x-dead-letter-exchange", EXCHANGE_DEAD_LETTER_Y);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key", ROUTING_KEY_YD);
//没有设置TTL
return QueueBuilder.durable(QUEUE_C).withArguments(arguments).build();
} /**
* 死信队列
* @return 队列
*/
@Bean("queueD")
public Queue queueD(){
return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
} /**
* 队列E,设置TTL 10s,过期后丢弃消息
* @return 队列
*/
@Bean("queueE")
public Queue queueE(){
Map<String, Object> arguments = new HashMap<>();
//设置TTL 单位是ms
arguments.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_E).withArguments(arguments).build();
} /**
* 队列和交换机绑定
* @param queueA 队列
* @param xExchange 交换机
* @return 绑定关系
*/
@Bean
public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with(ROUTING_KEY_XA);
} /**
* 队列和交换机绑定
* @param queueB 队列
* @param xExchange 交换机
* @return 绑定关系
*/
@Bean
public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueB).to(xExchange).with(ROUTING_KEY_XB);
} /**
* 队列和交换机绑定
* @param queueC 队列
* @param xExchange 交换机
* @return 绑定关系
*/
@Bean
public Binding queueCBindingX(@Qualifier("queueC") Queue queueC,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueC).to(xExchange).with(ROUTING_KEY_XC);
} /**
* 队列和交换机绑定
* @param queueD 队列
* @param yExchange 交换机
* @return 绑定关系
*/
@Bean
public Binding queueDBindingY(@Qualifier("queueD") Queue queueD,
@Qualifier("yExchange") DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with(ROUTING_KEY_YD);
} /**
* 队列和交换机绑定
* @param queueE 队列
* @param xExchange 交换机
* @return 绑定关系
*/
@Bean
public Binding queueEBindingX(@Qualifier("queueE") Queue queueE,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueE).to(xExchange).with(ROUTING_KEY_XE);
} }

用来通过接口发送消息的controller

package com.zjw.controller;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
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 java.util.Date; import static com.zjw.config.TtlQueueConfig.*; /**
* 发送ttl消息
* @author 朱俊伟
* @since 2022/09/06 23:36
*/
@AllArgsConstructor
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendTTLMsgController { private RabbitTemplate rabbitTemplate; /**
* 生产者发送消息
* @param message 消息
*/
@GetMapping("sendMsg/{message}")
public void sendMsg(@PathVariable String message) {
log.info("当前时间:{},发送一条信息给两个TTL队列:{}", new Date(), message);
rabbitTemplate.convertAndSend(EXCHANGE_X, ROUTING_KEY_XA, "消息来自ttl为10S的队列" + message);
rabbitTemplate.convertAndSend(EXCHANGE_X, ROUTING_KEY_XB, "消息来自ttl为40S的队列" + message);
} /**
* 生产者发送消息和过期时间
* @param message 消息
* @param ttlTime 过期时间,单位是毫秒
*/
@GetMapping("sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,
@PathVariable String ttlTime) {
log.info("当前时间:{},发送一条时长{}毫秒信息给队列QC:{}", new Date(), ttlTime, message);
rabbitTemplate.convertAndSend(EXCHANGE_X, ROUTING_KEY_XC, message, messagePostProcessor -> {
messagePostProcessor.getMessageProperties().setExpiration(ttlTime);
return messagePostProcessor;
});
} /**
* 生产者发送消息
* @param message 消息
*/
@GetMapping("sendDropMsg/{message}")
public void sendDropMsg(@PathVariable String message) {
log.info("当前时间:{},发送一条信息给过期丢弃的TTL队列:{}", new Date(), message);
rabbitTemplate.convertAndSend(EXCHANGE_X, ROUTING_KEY_XE, "消息来自ttl为10S的队列" + message);
}
}

测试

死信队列

访问:http://localhost:8080/ttl/sendMsg/helloQA QB队列中发送一个“hello”的消息

观察日志

// 生产者端
2024-01-10T20:12:28.735+08:00 INFO 11744 --- [nio-8080-exec-8] com.zjw.controller.SendMsgController : 当前时间:Wed Jan 10 20:12:28 CST 2024,发送一条信息给两个TTL队列:hello // 消费者端
2024-01-10T20:12:38.742+08:00 INFO 11548 --- [ntContainer#0-1] c.zjw.consumer.DeadLetterQueueConsumer : 当前时间:Wed Jan 10 20:12:38 CST 2024,收到死信队列的消息:消息来自ttl为10S的队列hello
2024-01-10T20:13:08.741+08:00 INFO 11548 --- [ntContainer#0-1] c.zjw.consumer.DeadLetterQueueConsumer : 当前时间:Wed Jan 10 20:13:08 CST 2024,收到死信队列的消息:消息来自ttl为40S的队列hello

消息在到达QA队列后10s后消息过期,存入消息队列QD后被消费者消费。

消息在到达QB队列后40s后消息过期,存入消息队列QD后被消费者消费。

自定义ttl消息

访问:http://localhost:8080/ttl/sendExpirationMsg/hello/20000QC队列中发送一个“hello”的消息,设置过期时间为20s.

访问:http://localhost:8080/ttl/sendExpirationMsg/world/1000QC队列中发送一个“world”的消息,设置过期时间为1s.

观察日志

// 生产者端
2024-01-10T20:21:35.795+08:00 INFO 5524 --- [nio-8080-exec-2] com.zjw.controller.SendMsgController : 当前时间:Wed Jan 10 20:21:35 CST 2024,发送一条时长20000毫秒信息给队列QC:hello
2024-01-10T20:21:37.921+08:00 INFO 5524 --- [nio-8080-exec-3] com.zjw.controller.SendMsgController : 当前时间:Wed Jan 10 20:21:37 CST 2024,发送一条时长1000毫秒信息给队列QC:world // 消费者端
2024-01-10T20:21:55.800+08:00 INFO 11548 --- [ntContainer#0-1] c.zjw.consumer.DeadLetterQueueConsumer : 当前时间:Wed Jan 10 20:21:55 CST 2024,收到死信队列的消息:hello
2024-01-10T20:21:55.801+08:00 INFO 11548 --- [ntContainer#0-1] c.zjw.consumer.DeadLetterQueueConsumer : 当前时间:Wed Jan 10 20:21:55 CST 2024,收到死信队列的消息:world

注意观察时间,我们虽然设置了“hello”的过期时间为20s,设置了“world”的过期时间为10s,但是在消费者端接受到的消息几乎是同时的

这是由于RabbitMQ会先检查先到的第一个消息“hello”的是否过期,并不会管“world”有没有到期,当“hello”过期后将它存到了QD死信队列,这时才会看后到的“world”有没有过期,发现它也过期了,就将“wrold”也存入了QD死信队列。

过期丢弃消息

访问:http://localhost:8080/ttl/sendDropMsg/helloQE队列中发送一个“hello”的消息,10s后消息没有被消费丢弃

总结

将过期时间设置在队列上和消息上各有优劣。

队列设置过期时间:

优点:消息不用在单独设置过期时间了,队列里的消息过期时间是一致的。

缺点:不能控制单个消息的过期时间。

消息设置过期时间:

优点:可以控制单个消息的过期时间。

缺点:如果先到的消息过期时间太长,会造成后到的消息可能已经过期了,但是还在等待的情况。

所以在开发中如果给消息设置了过期时间而不是而队列设置过期时间的话,上面的处理机制需要注意。

RabbitMQ消息的生存时间TTL(Time To Live)的更多相关文章

  1. [转载]RabbitMQ消息可靠性分析

    有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就能够叙述明白的,包括Kafka也是如此.可靠并不 ...

  2. RabbitMQ消息可靠性分析 - 简书

    原文:RabbitMQ消息可靠性分析 - 简书 有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就 ...

  3. golang监听rabbitmq消息队列任务断线自动重连接

    需求背景: goalng常驻内存任务脚本监听rbmq执行任务 任务脚本由supervisor来管理 当rabbitmq长时间断开连接会出现如下图 进程处于fatal状态 假如因为不可抗拒因素,rabb ...

  4. RabbitMQ消息可靠性、死信交换机、消息堆积问题

    目录 消息可靠性 生产者消息确认 示例 消费者消息确认 示例 死信交换机 例子 高可用问题 消息堆积问题 惰性队列 参考 消息可靠性 确保消息至少被消费了一次(不丢失) 消息丢失的几种情况: 消息在网 ...

  5. RabbitMQ消息队列入门及解决常见问题

    RabbitMQ消息队列 同步通讯和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应. 异步通讯:就像发邮件,不需要马上回复. 两种方式各有优劣,打电话可以立即得到响应 ...

  6. RabbitMQ消息队列(一): Detailed Introduction 详细介绍

     http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...

  7. RabbitMQ消息队列1: Detailed Introduction 详细介绍

    1. 历史 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有 ...

  8. (转)RabbitMQ消息队列(九):Publisher的消息确认机制

    在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...

  9. (转)RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

    在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇文章中,我们将会 ...

  10. (转)RabbitMQ消息队列(六):使用主题进行消息分发

    在上篇文章RabbitMQ消息队列(五):Routing 消息路由 中,我们实现了一个简单的日志系统.Consumer可以监听不同severity的log.但是,这也是它之所以叫做简单日志系统的原因, ...

随机推荐

  1. Q:ssh远程连接慢的原因排查

    连接linux服务器一般都是使用SSH远程连接的方式.有时,SSH连接速度很慢,大约30s左右,但是ping时一切正常. 问题原因 1.server的sshd会去DNS查找访问的client ip的h ...

  2. 清华大学推出的 DeepSeek 从入门到精通(104页)免费教程!

    前言 最近 DeepSeek 的出现让 AI 在国内掀起了一股浪潮,各大媒体.平台都在讨论和推广 DeepSeek,帮助各行各样使用 AI 不再有困难.今天大姚给大家分享一个由清华大学推出的.免费的: ...

  3. SMMS图床Java接口上传

    前言 个人项目开发中,网站建设中需要用到大量的图片以及用户上传的图片,如果服务器带宽小,磁盘容量小将所有的图片信息全部存储在服务器上不太现实,这里建议将图片数据存储在对象存OSS上或者将图片保存在图床 ...

  4. MOS管的寄生电容

    我们经常看到,在电源电路中,功率MOS管的G极经常会串联一个小电阻,几欧姆到几十欧姆不等,那么这个电阻用什么作用呢? 这个电阻的作用有2个作用:限制G极电流,抑制振荡. 限制G极电流MOS管是由电压驱 ...

  5. autMan奥特曼机器人-内置wx机器人的相关说明

    内置wx机器人的相关说明 内置wxbot机器人,经常有人说在群内无回复,做以下几个工作: 给群命名 通过机器人微信APP将此群加入到通讯录 重启autMan 内置微信机器人已经支持群名设置 例如转发时 ...

  6. Ubuntu详细的安装和配置ssh教程

    Ubuntu安装和配置ssh的步骤如下: 打开终端,输入以下命令安装ssh: sudo apt-get install openssh-server 安装完成后,启动ssh服务: sudo syste ...

  7. Ansible 数百台批量操作前期准备工作

    Ansible 数百台批量操作前期准备工作 背景: 当前有100台服务器在同一个内网,需要统一部署业务程序并且对主机修改主机名,只提供了一个文档host_user.txt,内容 " IP 用 ...

  8. Docker - 部署IT运维管理平台CAT

    原文链接:https://mp.weixin.qq.com/s/Ld9OLnmHP1IAc0Ofo-RzeQ 一.CAT介绍(略) 二.环境规划(略) 三.检查环境(略) 四.部署cat镜像 1.下载 ...

  9. FUSE,从内核到用户态文件系统的设计之路

    FUSE(Filesystem in Userspace)是一个允许用户在用户态创建自定义文件系统的接口,诞生于 2001 年.FUSE 的出现大大降低了文件系统开发的门槛,使得开发者能够在不修改内核 ...

  10. CPrimerPlus

    还没学 的 167页的wordcnt程序 199页的checking程序(太长了,不想看) 113页的第八章编程练习5(不想看) 125页的复习题9(有问题,有时间再来验证) 119页重定向和文件(n ...