RabbitMQ 常见八股:包括组成部分、消息的相关处理、持久化和集群等。

RabbitMQ

基础八股

RocketMQ 和 Kafka 和 RabbitMQ

RabbitMQ 如何实现消息的持久化

  • 交换机持久化:在声明交换机时, 把 durable 参数设置为 true;

  • 队列持久化:声明队列时,同样将 durable 参数设置为 true,消息会被存储在磁盘中,即使 RabbitMQ 服务器重启或崩溃时的行为。消息只存储在内存中,当 RabbitMQ 服务器重启或崩溃时,这些消息会丢失。

  • 消息持久化: 发送消息前,把消息的 deliveryMode 设置为 PERSISTENT。

  • 从 RabbitMQ 的 3.6.0 版本开始,就增加了 Lazy Queues 的模式,也就是惰性队列,在 3.12 版本之后,LazyQueue 已经成为所有队列的默认格式。惰性队列特征如下:接收到消息后直接存入磁盘而非内存。消费者要消费消息时才会从池畔中读取并加载到内存(也就是懒加载,可以提前缓存部分消息到内存,最多 2048 条),并且它支持百万条的消息存储。

复制代码
@RabbitListener(queuesToDeclare = @Queue(
    name = "lazy.queue",
    durable = "true,
    arguments = @Argument(name = "x-queue-mode", value = "lazy
))
public void listenLazyQueue(String msg) {
    log.info("接收到 lazy.queue 的消息:{}", msg);
}
                
 
@Bean
public Queue lazyQueue() {
    return QueueBuilder
                .durable("lazy.queue")
                .lazy() // 开启 x-queue-mode 为 lazy
                .build();
}

镜像队列(Mirrored Queue) 在高可用性的需求下,可以配置镜像队列,它们会将消息复制到集群中的每一个节点,以防止单个节点故障导致的数据丢失。消息写入磁盘的过程不仅仅可通过 durable 参数和 delivery_mode 设置,还可以通过 confirm 模式来优化,确保消息在达到磁盘前不会被消费者处理。使用 confirm 模式可以获得消息何时完全持久化的信息。

磁盘写入策略: RabbitMQ 提供了多种磁盘写入策略来保证不同程度的持久化和性能,应该根据业务需求选择合适的策略,比如 Lazy Queues 模式可以适用于消息积压较多的场景。

消息确认机制(ACKS): RabbitMQ 提供的消息确认机制也是保证消息可靠传递的重要部分,消费者在处理完消息后发送 ack,只有收到 ack 的消息才会被服务器删除。

RabbitMQ 如何确保消息不会丢失呢

生产者的可靠性
  • 生产者发送消息时连接 MQ 、由于网络波动 ,可能连不上 MQ 服务器 ,因此消息就会发送失败

  • 生产者发送消息到达 MQ未找到 Exchange,找不到交换机就会导致消息丢失。

  • 生产者发送消息到达 MQ 的 Exchange 后,未找到合适的 Queue,这个原因可能在于发送的路由键与绑定的路由键不同 ,导致无法正确分发消息,导致消息丢失。

针对生产者的可靠性问题,首先可以设定一个重试机制,当发送消息第一次连接 MQ 服务端失败时,可以添加重试机制,有可能因为网络波动问题第二次就能连接上;

复制代码
spring:
    rabbitmq:
        hotst: 127.0.0.1
        port: 5672
        virtual-host: /
        username: guest
        password: guest
        connection-timeout: 1s
        publisher-confirm-type: correlated # 生产者会异步等待确认
        publisher-returns: true
        template:
            retry:
                enabled: true # 重启连接
                initial-interval: 1000ms # 首次失败后下次重连时间间隔
                multiplier: 1    # 重连时间倍数
                max-attempts: 3  # 重连次数

生产者发送消息到 MQ 后,如果服务端接收了该条信息并成功转发路由到某个队列,则返回 ack 表示确定收到消息;如果服务端接收到信息但是路由失败,通过 PublishReturn 返回路由异常原因并则会 ACK,其他情况都会返回 NACK ,可以在配置件中开启生产者确认机制。

  • 生产者发送消息后,RabbitMQ 会将这些消息存储在队列中,每条消息会分配一个唯一的序列号。
MQ 服务端的可靠性

MQ 服务器发送宕机,导致服务器中队列存储的数据消失。

MQ 服务端的可靠性是为队列、交换机、消息添加持久化机制,保证消息都能被持久化到磁盘上,具体实现就是惰性队列。

消费者的可靠性

消费者就收到消息 时,可能会发送业务异常 ,或者这条消息的格式存在错误 ,这就会导致消息 没办法保存下来,就会立刻丢失

消费者的可靠性主要由消费者确认实现,当消费者处理完一条消息后,会向服务端发送一个回执,如果成功处理返回 ack;如果出现业务异常返回 nack 重新投递消息;如果消息处理或校验异常,返回 reject 直接丢弃消息;可以在配置文件中打开消费确认机制。

如果消息处理成功,消费者调用 basicAck 方法发送 ACK 信号;如果处理失败或者发生错误,消费者可以调用 basicNackbasicReject 方法,拒绝消息。

复制代码
spring:
    rabbitmq:
        host: 127.0.0.1
        port: 5672
        virtual-host: /
        username: guest
        password: guest
        listener:
            simple:
                prefetch: 1
                acknowledge-mode: auto

acknowledge-mode: auto 表示打开了消费确认。

在消费确认这种可靠性技术下,如果发生业务异常会重新入队,但是如果一条消息一直错误一直入队会消耗消息队列资源,因此可以开启本地重试机制,该机制会在本地重新处理消息,不会重新入队出队。

复制代码
spring:
    rabbitmq:
        host: 127.0.0.1
        port: 5672
        virtual-host: /
        username: guest
        password: guest
        listener:
            simple:
                retry:
                    enabled: true

并且在本地重试完后,也有继续的处理机制,第一种是直接丢弃,第二种是重新入队,第三种是转发给别的交换机;推荐的是第三种,通过添加一个 MessageRecoverer 接口实现。

复制代码
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
    return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}
集群配置与故障转移

为了提高 RabbitMQ 的可用性,可以设置集群并配置自动故障转移。当一个节点发送故障时,其他节点会自动接管其工作。通过 rabbitmqctl cluster_status 可以查看集群状态,并使用 rabbitmqctl join_cluster 命令来添加新节点。

如何在 RabbitMQ 中处理消息的重复消费问题

RabbitMQ 采用消息确认机制来确保消息被成功处理。当消费者从队列中获取消息并处理完业务逻辑后,需要向 RabbitMQ 发送一个确认信号(ack),以此告别 RabbitMQ 这条消息已经处理完毕,RabbitMQ 可以将该消息从队列中移除。在实际运行中,消费者处理完消息准备发送 ack 给 RabbitMQ 时,如果出现网络故障,比如网络延迟、断连等情况,就会导致 ack 消息无法成功抵达 RabbitMQ。由于 RabbitMQ 没有收到 ack,按照其确认机制,它会认为消费者没有成功处理这条消息。当网络恢复后,RabbitMQ 并不直到消费者其实已经处理过该消息,它会依旧消息未被确认的状态,重新将这条消息发送给消费者。此时,消费者就会再次接受到同一条消息并进行处理,进而导致重复消费问题的出现。

在 RabbitMQ 中处理消息的重复消费问题,可以通过以下几种方式来确保消息只能被正确处理一次。

  • 消息的幂等性:确保消息处理方法是幂等的,多次处理相同的消息不会导致不同的结果。

  • 消息确认机制:消费者在处理完消息后,显示地向 RabbitMQ 发送一个确认(ack)。如果 RabbitMQ 收到了这个确认,意味着消费者成功处理消息,RabbitMQ 就会从队列中删除该消息。如果 RabbitMQ 没有收到这个确认,它会认为消息没有被处理,会重新投递该消息给其他消费者。

  • 消息持久化与日志记录:将已处理的消息记录在数据库或者 Redis 中,在处理新消息时,先检查该消息是否已经被处理过,避免重复处理。

  • 使用唯一消息ID:每一条消息都生成一个唯一的 id,与消息一起投递给消费者。消费者接收到消息后处理自己的业务,业务处理成功后将消息 ID 保存到数据库,如果下次又收到相同消息,去数据库查询判断是否存在,存在则为重复消息放弃处理。

如何在 RabbitMQ 中实现消息幂等性呢

在 RabbitMQ 中实现消息幂等性可以通过记录已经处理的消息 ID 来避免重复处理。从大体上说:

1)在消息属性中包含一个唯一的消息 ID。

2)消费者在处理消息前先检查这个消息 ID 是否已经处理过。

3)如果消息 ID 未被记录过,则处理消息并记录这个消息 ID

4)如果消息 ID 已经存在,直接跳过该消息。

去重时效策略

为了防止消息 ID 列表无限增长,可以根据具体业务需求定制一种 ID 的失效策略,比如一个固定时长内的消息才需要幂等性检查。

消息属性设置

可以使用 RabbitMQ 的 basicProperties 来包含一个类似 UUID 的唯一标识符。为了提升查找已经处理过的消息 ID 的效率,可以使用高性能、支持快速查找的数据结构。使用持久化存储(例如数据库、Redis)保持已经处理过的消息 ID。这样在系统重启或者崩溃后,仍然可以查询到哪些消息已经被处理过。

你是执行消息之前插入ID还是之后插入ID? 如果之前插入ID有没有一种情况,你插入ID之后你的业务没有执行完就宕机了,然后重复消费的时候,误以为你这个业务已经执行过。如果你是之后插入ID,那有没有可能你的业务已经执行完在插入ID之前就宕机了,然后重复消费的时候他误以为这个业务没有执行过。可以用事务保证操作的原子性,避免上述的情况,要么都成功要么都失败。

复制代码
@Configuration
public class RabbitMqConfig {
    @Bean
    public MessageConverter messageConverter(ObjectMapper objectMapper) {
        Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);
        converter.setCreateMessageIds(true);
        return converter;
    }
}

RabbitMQ 中的消息如何确保顺序性

RabbitMQ 同个队列中是可以保证其有序性的,底层是采用 FIFO 队列,但多个队列之间是没办法的。

1)单消费者 + 单队列:利用队列天然的 FIFO 特性,严格串行化。

2)对消息进行分区处理,通过某种键值(可以是消息属性中的某个字段,例如用户ID、订单ID 等)将消息路由到不同的队列,每个队列由一个独立的消费者处理。这样既可以保证特定分组内消息的顺序性,又能并行处理不同分组的消息以提高系统吞吐量。

3)消息序列号:应用层通过序列号和外部状态管理顺序。

在 RabbitMQ 中,如何防止消息堆积

增加消费者数量

增加消费者数量最直接的方法是部署更多的消费进程或实例,例如在 Kubernetes 中扩展 Pod 数量。这样可以均衡分配消息,减轻单个消费者的负担,但需要注意的一点是,增加消费者本身带来的资源消耗也要被纳入考虑范围。

优化处理逻辑

优化消费者的处理逻辑非常关键,特别是数据库和网络操作。如果处理逻辑过慢,考虑使用异步处理、批量处理或者是借助缓存来提升效率。

消息预取设置(Prefetch Count)

RabbitMQ 提供的 Prefetch Count 功能可以限制消费者在开启确认机制时一瞬间获取的消息数量,从而防止消息堆积在某个消费者中未被及时消费。设置合理的预取值,比如 10,可以保证消费者充分使用但也不至于因为消息处理慢而阻塞。

消息过期时间(TTL)和死亡队列

为消息队列设置 TTL 可以避免消息长时间未被消费者造成堆积。TTL 到期的消息会被转发到死信队列(DLQ),这就给了系统一个机会来处理垃圾数据、重试或者清理。可以针对业务需求灵活配置 TTL 和绑定的死信队列。

监控和报警

使用 RabbitMQ 的管理插件或者 Prometheus 之类的监控工具来观察队列状态。通过设置适当的报警阈值,这就给了系统一个机会来处理垃圾数据、重试或者清理。可以针对业务需求灵活配置 TTL 和绑定的死信队列。

使用集群与负责均衡

利用 RabbitMQ 集群可以有效提高系统的处理能力和容错能力。为了增加系统的水平扩展能力,集群模式下可以使用诸如 HAProxy 或 Kubernetes ingress 控制器进行负责均衡,从而分散生产者和消费者的压力分布。

如何在 RabbitMQ 中实现消息的批量消费

Spring Boot 中可以配置批量消费模式,通过设置 batchSize 和 consumerBatchEnabled 等参数,使消费者一次拉取多条消息。

  • batch-size: 10 # 每次批量消费的消息数量

  • consumer-batch-enabled: true # 启动批量消费

  • prefetch:是限制消费者本地未确认消息的最大数量

  • batch-size 定义消费者单次从本地缓存中取出多少条消息进行业务处理

实现批量消费的监听器在批量消费模式下,Spring 会将多条消息一起传递给消费者,消费者可以一次性处理多条消息。我们可以通过实现 List<Message> 类型的参数来接受一批消息。

RabbitMQ 的流控机制是什么,有了解过吗?

流控机制是防止消息队列系统过载的机制,通过限制传输速率或暂停消息传输以保障系统稳定可靠,其触发常与内存/磁盘使用相关。有了它可以在资源耗尽前,提前限流,避免压垮系统。

流控模式有三种:

内存流控:内存使用率接近配置水位性(默认 40% 物理内存)时,限制新消息接收。

磁盘流控:磁盘空间不足时,暂停新消息写入,直至磁盘恢复。过设置参数 disk_free_limit 来指定触发限值,例如:disk_free_limit.absolute = 5GB

网络流空:调整 TCP 窗口或限流应对网络拥堵,保障消息传输质量。

当 RabbitMQ 队列满了时该怎么办

队列满的触发条件

队列达到最大长度限制(通过 x-max-length 参数设置消息数量,或 x-max-length-bytes 设置内存占用内存大小)。

磁盘空间不足(针对持久化队列),此时 RabbitMQ 会阻塞(block) 生产者以防止系统崩溃。

默认行为:丢弃或拒绝消息

丢弃新消息(默认) :当队列满时,新的消息会被直接丢弃(drop-head 策略,即删除队头的旧消息)。

拒绝新消息 :如果生产者设置了 mandatory 标志,被丢弃的消息会通过 basic.return 返回给生产者。如果启用了发布确认(Publisher Confirm),生产者会收到 NACK。

溢出策略 (Overflow Behavior)

通知 x-overflow 参数可以配置队列满时的行为:

drop-head(默认):删除队列头部的旧消息,为新信息腾出空间。

reject-publish: 直接拒绝新消息(通过 NACK 或 basic.return),保留队列现有消息。

reject-publish-dlx: 拒绝新消息,并将其路由到死信交换机(需配置死信队列)。

生产者端的响应

同步发布 :如果生产者使用 channel.basicPublish 同步发送,可能会抛出异常或收到 basic.return

异步发送:启用 Publisher Confirm 后,生产者会收到 NACK,需通过回调处理失败消息(如重试或记录日志)。

消费者端的处理

消费者无感知:队列满时仅影响生产者,消费者仍可正常消费现有消息。

如果队列设置了 TTL(过期时间),旧消息过期后会自动释放空间。

预防措施

通过 RabbitMQ 管理插件或 API 监控 queue_length,提前预警。使用 rabbitmqctl set_policy 动态修改队列长度限制。配置死信交换器(DLX)处理被拒绝的消息。增加节点或使用镜像队列分散负载。

复制代码
// 创建队列时设置最大长度和溢出策略
Map<String, Object> args = new HashMap<>();
args.put("x-max-length", 1000); // 最大消息数
args.put("x-overflow", "reject-publish"); // 拒绝新消息
channel.queueDeclare("my_queue", false, false, false, args);

RabbitMQ 队列满时的行为是可控的,关键在于合理配置 x-max-lengthx-overflow,并结合监控和死信队列处理异常场景。生产端应实现消息重试或降级逻辑,以确保系统的可靠性。

RabbitMQ 的高可用集群有哪些?如何实现

经典镜像队列 (Mirrored Queues)

通过复制队列的消息和状态到多个节点,实现消息的高可用。集群中一个节点是主节点(Master),其他节点是镜像节点(Slave)。消费者连接到主节点,主节点负载投递和同步。

在队列声明时,设置策略 (Policy) 指定队列为镜像队列。通过设置 ha-modeha-params 参数控制镜像的节点数或指定哪些节点做镜像。

复制代码
rabbitmqctl set_policy ha-all "" '{"ha-mode":"all"}'
​
rabbitmqctl set_policy ha-two "^ha\." '{"ha-mode":"exactly","ha-params":2}'

简单直观,支持大部分 RabbitMQ 功能。缺点是:同步性能瓶颈,网路分区时容易出现脑裂,维护复杂。

Quorum 队列(Quorum Queues)

基于 Raft 协议实现的复制队列,代替经典镜像队列。通过多数节点共识保证数据一致性,自动选举 Leader,副本节点同步日志。

复制代码
rabbitmqadmin declare queue name=my_quorum_queue durable=true arguments='{"x-queue-type":"quorum"}'

优点:强一致性、自动故障恢复、易维护,适合关键业务。缺点:不支持优先级队列等部分高级特性,性能上有开销。

联邦模式

用于跨数据中心或跨网络的消息复制,节点间通过交换机和队列绑定实现消息的转发和复制。它不是强一致性,更多是异步复制。

通过配置联邦插件,设置 upstream(上游节点)和策略。配置交换机、队列绑定规则,实现消息传递。

Shovel 插件

用于将消息从一个 RabbitMQ 集群自动复制到另一个集群。它类似于联邦,但配置更加灵活。

通过配置 Shovel 插件,指定源和目标地址,自动搬运消息。

RabbitMQ 插件

1)rabbitmq_management: 这个一个用于管理的 RabbitMQ 的 Web 控制台插件,提供了图形界面来监控和管理 RabbitMQ 容易。

2)rabbitmq_federation:该插件允许 RabbitMQ 节点和集群跨越广域网进行通信。

3)rabbitmq_delayed_message_exchange:用于支持延迟消息,通过这个插件你可以发布消息,然后在指定的时间后才进行投递。

4)rabbitmq_auth_backend_ldap:这个插件允许 RabbitMQ通过 LDAP (轻量级目录访问协议) 进行用户认证。

如何解决 RabbitMQ 中的分区问题(网络问题)

在一个 RabbitMQ 集群中,某些节点由于网络故障而相互无法通信,这可能会导致消息丢失、重复消费、节点状态不一致等问题。

解决方法:

适当规划队列和消息分布策略,尽量保证队列和消费者的分布均衡,减少跨节点通信的需求,从而降低分区影响。在 RabbitMQ 3.8 之后的一个特性,使用 Raft 协议来保证消息的高可用性和一致性,相比传统镜像队列更加稳定和高效。

扫盲八股

RabbitMQ 你了解过吗?

RabbitMQ 是一个开源的消息代理软件,实现了高级消息队列协议(AMQP),允许应用程序之间解耦,提供了可靠的消息传递和高可用的消息队列。主要的应用场景包括,将繁重的任务异步化处理,使用消息队列来触发和通知不同的系统组件,通过消息队列接收和处理实时数据,适用于流数据处理的应用。

主要由以下核心组件组成:

1)Producer(生产者):负责发送消息到交换机。

2)Exchange(交换机):接受并路由消息到队列,根据绑定键将消息分配到一个或多个队列。

3)Queue(队列):消息的存储地点,消费者从队列中读取消息。

4)Consumer(消费者):接收并处理队列中的消息。

5)Binding(绑定):定义交换机和队列之间的路由规则。

6)Routing Key(路由键):用于交换机到队列的路由规则。

7)Virtual Host(虚拟主机):逻辑分组,用于隔离不同应用的资源。

8)Connection(连接):RabbitMQ 的客户端与服务器之间的网络连接。

9)Channel(信道):在连接中的虚拟连接,进行消息的读写操作。

10)Direct Exchange(直连交换机):根据精确的路由键发送消息到绑定的队列,适用于点对点的消息传输。

11)Fanout Exchange(扇出交换机):不考虑路由键,把消息广播到所有绑定的队列,适用于广播的消息传递,如系统日志。

12)Topic Exchange(主题交换机):根据模式匹配的路由键发送消息,如果希望某些队列能够接收某些特定模式的消息,可以使用通配符形式的路由键。适用于发布/订阅模式,例如新闻分类。

13)Headers Exchange(头交换机):根据消息的头部信息中的键值对来匹配,消息将被路由到匹配的队列。

什么是 RabbitMQ 中的虚拟主机(vhost)

vhost 它是一个逻辑上的隔离概念,用于隔离不同的应用或租户。每个虚拟主机可以拥有自己独立的队列、交换机、绑定、权限等资源。这样,多个相互独立的应用可以共存在一台 RabbitMQ 服务器上而不会相互影响。

多租户架构

在多租户环境中,多个用户或应用共享同一个 RabbitMQ 实例,每个用户或应用在各自的 vhost 中活动。这保证了数据和资源的隔离,同时也简化了管理。

权限管理

提供了细粒度的权限控制机制。你可以在某个 vhost 上为用户分配特定的角色,如管理员、读者、写者等。通过这种方式,可以严格控制哪些用户可以访问哪些资源。

优化资源使用

通过 vhost,可以对不同的应用进行资源和分配,提高资源的利用率。例如,在一个高负载的系统中,可以为不同的应用分配不同的 vhost,以便进行更加精细的性能调优和资源管理。

管理工具和 API

RabbitMQ 的管理插件和 API 可以方便地查看和管理 vhost。管理员可以通过这些工具查看每个 vhost 中的资源使用情况,有助于进行系统的监控和运维。

Simple 简单模式

不配置交换机,生产者直接发送给队列(实际使用了默认的交换机),消费者监听队列,队列与消费者是 1 对 1 的关系。

work 工作模式

公平分发和不公平分发,可以在消费端获取消息时将 channel 的参数 basicQos 设为 1(默认是 0),那么就会在消息分发时优先选择空闲的消费者分发,如果不存在空闲队列,那么还是按公平分发。

预取值:看作是规定的消费者等待消费队列内部期望的队列长度。比如消费 C1 是 2,C2 是 3,那么开始的消息会先分配给 C1,直到 C1 中等待消息的消息队列长度为 2 时,下一个消息才会分配给 C2,然后 C2 也积累了 3 个消息后,继续 C1、C2 轮流分配。预期值默认为 0,所以默认情况就是消费者轮流被分配消息。配置方式也是设置消费者端的 channel 对象的 basicQos 参数。

public/subscrible 发布订阅模式

交换机是 fanout 类型(扇出)。交换机会将接收的消息发送给所有与其绑定的队列。

RPC 模式

支持生产者和消费者不在同一个系统中,即允许远程调用的情况。通常,消费者作为服务端,放置在远程的系统中,提供接口,生产者调用接口,并发送消息。

首先对于 RPC 请求,客户端发送一条带有两个属性的消息:replyTo 设置为仅为请求创建的匿名独占队列和 correlationId,设置为每个请求的唯一 id 值。请求被发送到 rpc_queue 队列,RPC 工作进程(即:服务器)在队列上等待请求。当一个请求出现时,它执行任务,并使用 replyTo 字段中的队列将结果发回客户机。客户机在回应消息队列上等待数据,当消息出现时,它检查 correlationId 属性,如果匹配请求中的值,则向程序返回该响应数据。因此客户端即是发送者也是消费者,在请求发送给队列 rpc_queue 之后,服务器会监听这个队列,获取后处理,处理完成将返回数据消息发给队列 reply_to,而客户端也会监听这个队列,最终实现得到结果数据。

扩展知识

它的关键特性是:可以将消息存储在磁盘上,防止数据丢失。支持集群模式和镜像队列,确保在节点故障时系统仍然可用。提供交换器来灵活控制消息到队列的路由,可以根据不同的路由规则实现不同的消息分发策略。

除此之外,RabbitMQ 可以保证消息在 RabbitMQ 重启后不丢失。Confirm 模式:生产者可以通过该模式确认消息已被 RabbitMQ 正常处理。Delayed Queue:支持消息在一定时间后再进行消费。Dead Letter Queue: 消息在超时或被拒绝后存储的队列,用于后续处理。

一般来说可以在下单高峰时,将订单处理任务异步化处理。或者在收集大量分布式系统的日志数据,用消息队列来保证日志数据的可靠接收和处理。或者在微服务架构中,服务间的通信可以通过消息队列进行解耦,提高系统的可维护性和可扩展性。

声明一个队列,有哪些必要的参数

1)队列名称 2)是否持久化 3)是否排他 4)是否自动删除(autoDelete)5)额外参数(arguments): 比如 TTL 和 DLX(Dead-letter Exchange): 当消息被拒绝(rejected) 或者过期(expire)时,可以指定一个 Dead-letter Exchange 来处理这些消息。

prefetch 参数的作用是什么

prefetch 参数主要用于限制消息消费者(即消费者端)可以同时接收并未确认消息的数量。简单来说,prefetch 参数帮助控制消息处理的流量,使得消费者不会被淹没在大量并发的消息处理任务中。

在设置和调整 prefetch 参数时,监控消费者的处理速度、消息反馈时间和系统资源利用率,以做出科学合理的调整。

应用场景负载均衡 ,例如某些消费者可能希望一次处理更多消息而其他消费者则希望一次处理更少。避免内存溢出 :大量未处理消息堆积在内存中可能导致内存溢出,通过设置较小的 prefetch 值可以防止这种情况,保证系统的稳定性。优先级处理:更高优先级的消息可以被及时处理,因为低优先级的消费者不会一次性占用太多的消息处理资源。

什么是 RabbitMQ 中的死信队列(DLX)?

当一个队列中的消息满足下列情况之一,就会成为死信(dead letter):

  • 要投递的队列消息堆积满了,最早的消息可能成为死信。

  • 消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费

  • 消费者使用 basic.reject 或 basic.nack 声明消费失败,并且消息的 requeue 参数设置为 false

如果队列通过 dead-letter-exchange 属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。normal.queue: 是普通业务队列,负责暂存未过期的消息。它通过 dead-letter-exchange = dlx.direct 声明了 "死信策略" ------- 当消息在这队列里触发 "死信条件" (比如 TTL 过期),就会被转发到 dlx.direct 交换机。

dlx.queue: 是死信队列,因为它绑定了 dlx.direct(死信交换机)。当 normal.queue 里的消息变成死信后,会先到 dlx.direct,再路由到 dlx.queue,最终被 consumer 消费。

复制代码
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "dlx.queue", durable = "true"),
    exchange = @Exchange(name = "dlx.direct", type = ExchangeTypes.DIRECT),
    key = {"hi"}
))
public void listenDlxQueue(String message) {
    log.info("消费者监听到  dlx.queue 的消息:【{}】", message);
}
​
@Bean
public DirectExchange normalExchange() {
    return new DirectExchange("normal.direct");
}
​
@Bean
public Queue normalQueue() {
    return QueueBuilder
            .durable("normal.queue")
            .deadLetterExchange("dlx.direct")
            .build();
}
​
@Bean
public Binding normalExchangeBinding(Queue normalQueue, DirectExchange normalExchange) {
    return BindingBuilder.bind(normalQueue).to(normalExchange).with("hi");
}
​
​
@Test
void testSendDelayMessage() {
    rabbitTemplate.convertAndSend("normal.direct", "hi", "hello", message -> {
        message.getMessageProperties().setExpiration("10000");
        return message;
    });
}

如何在 RabbitMQ 中配置消息的 TTL(过期时间)

配置 TTL 有两种方式,一种是队列级别的 TTL,另一种是消息级别的 TTL。

队列级别的 TTL: 可以在声明队列时通过设置 x-message-ttl 参数来指定队列中所有消息的 TTL。

复制代码
// Java 示例(使用 RabbitMQ 的官方客户端)
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 60000); // 设置队列的 TTL 为 60000 毫秒(60 秒)
channel.queueDeclare("myQueue", false, false, false, args);

消息级别的 TTL:可以在发送消息时通过 AMQP.BasicProperties 属性指定单个消息的 TTL。

复制代码
// Java 示例(使用 RabbitMQ 的官方客户端)
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
        .expiration("60000") // 设置消息的 TTL 为 60000 毫秒(60s)
        .build();
​
channel.basicPublish("", "myQueue", props, "Hello, World!".getBytes());

TTL 是一个非常重要的机制,主要用于防止消息在队列中无限期积压,导致系统资源的耗尽,此外,合理设置 TTL 还可以帮助提高系统的吞吐量和响应速度。

在 RabbitMQ 中,如何实现延迟消息

可以通过死信交换机来实现消息延迟:通过给消息设置 TTL,让消息过期后被发送到死信交换机,然后再从死信交换机重新发布消息实现消息延迟。

  1. 生产者发送消息:设置消息 TTL 或队列 TTL。

  2. 消息过期:到达 TTL 时间后未被消费

  3. 转发到死信交换机:过期消息被路由到配置的 DLX

  4. 死信队列消费:绑定 DLX 的队列接收消息并处理

基于死信虽然可以实现延迟消息,但是太麻烦了。因此 RabbitMQ 社区提供了一个延迟消息插件来实现相同的效果。

  1. rabbitmq_delayed_message_exchange

  2. 这个插件可以将普通交换机改造为支持延迟消息功能的交换机,当消息投递到交换机后可以暂停一定时间,到期后再投递到队列。

复制代码
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "delay.queue", durable = "true"),
    exchange = @Exchange(name = "delay.direct", delayed = "true"),
    key = "delay"
))
​
public void listenDelayMessage(String msg) {
    log.info("接收到 delay.queue 的延迟消息:{}", msg);
}
​
package com.itheima.consumer.config;
​
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Slf4j
@Configuration
public class DelayExchangeConfig {
​
    @Bean
    public DirectExchange delayExchange(){
        return ExchangeBuilder
                .directExchange("delay.direct") // 指定交换机类型和名称
                .delayed() // 设置delay的属性为true
                .durable(true) // 持久化
                .build();
    }
​
    @Bean
    public Queue delayedQueue(){
        return new Queue("delay.queue");
    }
    
    @Bean
    public Binding delayQueueBinding(){
        return BindingBuilder.bind(delayedQueue()).to(delayExchange()).with("delay");
    }
}
​
​
// 发送消息时,必须通过 x-delay 属性设定延迟时间
@Test
void testPublisherDelayMessage() {
    // 1.创建消息
    String message = "hello, delayed message";
    // 2.发送消息,利用消息后置处理器添加消息头
    rabbitTemplate.convertAndSend("delay.direct", "delay", message, new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 添加延迟消息属性
            message.getMessageProperties().setDelay(5000);
            return message;
        }
    });
}
相关推荐
VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue房屋租赁管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
A-程序设计1 小时前
基于Django短视频推荐系统设计与实现-(源码+LW+可部署)
后端·python·django
古城小栈2 小时前
Go中 巧妙解决 同主版本多子版本共存
后端·golang
心灵宝贝2 小时前
sw_64架构 docker-ce-cli rpm 安装方法(附安装包)
后端·spring·spring cloud
IT_陈寒2 小时前
Redis性能翻倍的5个冷门技巧:从缓存穿透到集群优化实战指南
前端·人工智能·后端
聆风吟º2 小时前
【Spring Boot 报错已解决】Spring Boot接口报错 “No converter found” 解决手册
java·spring boot·后端
美味小鱼2 小时前
DupFinder:一个用 Rust 编写的高性能重复文件查找工具
开发语言·后端·rust
苦学编程的谢2 小时前
RabbitMQ_2_RabbitMQ快速入门
linux·centos·rabbitmq
Victor3562 小时前
Redis(160)Redis的安全问题如何解决?
后端