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/hello 向QA 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/20000 向QC队列中发送一个“hello”的消息,设置过期时间为20s.

访问:http://localhost:8080/ttl/sendExpirationMsg/world/1000 向QC队列中发送一个“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/hello 向QE队列中发送一个“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. 百万架构师第三十七课:RabbitMq:高可用集群搭建步骤|JavaGuide

    安装环境 Centos-7 三台虚拟机 192.168.8.150(磁盘节点) 192.168.8.45 (内存节点) 192.168.8.40 (内存节点) 一.安装Erlang 1.erlang ...

  2. go测试跨包代码覆盖率

    Golang虽然只是一门编程语言,但也为我们提供了不少工具,其中测试工具是最常用的,大概 前提概要 以前看书,只说了用什么工具去做覆盖率,和基本的使用,当时看了也没想太多.后面真正做项目了,老大要求比 ...

  3. STC15F104E的外部中断工作异常

    STC15F104E使用了外部中断,发现中断工作有时会失效,必需重新上电才能恢复,使用中不时会失效. 1 /********************************************** ...

  4. nginx 如何强制跳转 https

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

  5. QT5笔记: 21. QStandardItemModel

    QStandardItemModel 存放数据 QItemSelectionModel 选择项模型 例子:本例子中QListView 没有做任何处理,只是拖放至ui文件,设置了布局 mainwindo ...

  6. Hi3516EV200 编译环境配置及交叉编译软件包

    基础信息 OS: Ubuntu 16.04 xenial SDK 版本: Hi3516EV200R001C01SPC012 - Hi3516EV200_SDK_V1.0.1.1 SDK 包路径:Hi3 ...

  7. mysql导出指定数据或部份数据的方法

    https://www.jb51.net/article/47525.htm 1.新建一个表,比如new-table,包含所要导出的字段的名称,比如a,b,c2.使用insert into new-t ...

  8. 关于我第二周学习kotlin这门语言

    有关kotlin的知识点: 在学习lambda之前,我们先了解一下什么是lambda,简答来说就是一小段代码块,并且我们可以将这个代码块在函数之间传递,这是函数式编程的一个重要特性. 通常我们会需要一 ...

  9. AXUI一个面向设计的UI前端框架,好用

    以下是官方介绍: ax的中文意义是:斧子,读音[aeks],取其攻击力强.简单实用之意为本前端框架命名.本团队开发了诸多网站项目,使用了许多常见的前端框架,结合实际项目经验,借鉴了同行的经验,特自主开 ...

  10. 市场教父 André Kostolany

    大家好,我是 沃伦·爱德华·巴菲特(Warren Edward Buffett,1930 年 8 月 30 日 - ) 查尔斯·托马斯·芒格(Charles Thomas Munger,1924 年 ...