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. Ansible之二playbook

    反星系 连接https://galaxy.ansible.com下载相应的roles 列出所有安装的   galaxy ansible-galaxy  list 安装galaxy     ansibl ...

  2. 流程控制之break、continue和goto

    #### 实例1: ```javapackage com.yeyue.struct; public class BreakDemo { public static void main(String[] ...

  3. mongoDb 的启动方式

    参考地址:https://www.cnblogs.com/LLBFWH/articles/11013791.html 一. 启动 1. 最简单的启动方式,前台启动,仅指定数据目录,并且使用默认的271 ...

  4. nginx 如何强制跳转 https

    本项目 nginx 作为代理服务 项目上线,客户说要加个安全证书 ,于是安全证书是加上了,可是htttp和https都能访问网站,客户要求不行必须强制用带有https的地址访问 开整 这是 http ...

  5. 关于 False、True、0、1、tinyint(1) 的说明

    MySQL 保存 Boolean 值时,用 1 代表 TRUE,0 代表 FALSE:类似一个 bit 位,默认没有数据,即为 0,也即 Faslse MySQL 存储 Boolean 值的类型为 t ...

  6. JS中的0和php中的0

    请注意:包含 0 的字符串 "0" 是 true 一些编程语言(比如 PHP)视 "0" 为 false.但在 JavaScript 中,非空的字符串总是 tr ...

  7. 当我老丈人都安装上DeepSeek的时候,我就知道AI元年真的来了!

    关注公众号回复1 获取一线.总监.高管<管理秘籍> 春节期间DeepSeek引爆了朋友圈,甚至连我老丈人都安装了APP,这与两年前OpenAI横空出世很不一样,DeepSeek似乎真的实现 ...

  8. vue watch监听路由变化

    vue watch监听路由变化 // 监听 this.$route.path // watch监听非DOM元素的改变 watch:{ '$route.path':function(to,from){ ...

  9. 搭建docker swarm集群实现负载均衡

    Swarm简介:Swarm是Docker官方提供的一款集群管理工具,其主要作用是把若干台Docker主机抽象为一个整体,并且通过一个入口统一管理这些Docker主机上的各种Docker资源.Swarm ...

  10. ISODate时间转换

    private function formatISODate($dateTime) { $date = date("Y-m-d", strtotime($dateTime)); $ ...