【每日八股】学习 RocketMQ Day3:进阶(二)

文章目录

  • 复习之前内容
    • 为什么使用消息队列
    • 为什么选择 RocketMQ?
    • RocketMQ 的优缺点?
    • 谈谈你对 RocketMQ 的理解
    • 消息队列有哪些类型?
    • RocketMQ 采用哪种消息队列模型?
    • 消息的消费模式了解吗?
    • 了解 RocketMQ 的基本架构吗?
    • 详细解释一下 RocketMQ 基本架构中四部分的作用?
      • NameServer
      • Broker
      • Producer
      • Consumer
    • 如何保证消息的可用性/可靠性/不丢失呢?
    • 如何处理消息重复的问题呢?
  • 学习 RocketMQ Day3:进阶(二)
    • 怎么处理消息积压?
    • 顺序消息如何实现?
    • 如何实现消息过滤?
      • Tag 过滤(最常用)
      • SQL92 表达式过滤(复杂过滤)
      • 消息 Key 过滤
      • Filter Server
    • 延时消息了解吗?
      • RocketMQ 如何实现延时消息?
    • 怎么实现分布式消息事务?半消息?
      • 事务消息核心概念
      • 基于半消息实现分布式事务的关键步骤
    • 介绍一下 RocketMQ 的死信队列
    • 如何保证 RocketMQ 的高可用?

复习之前内容

上一次学习 RocketMQ 的相关知识已经可以追溯到十多天之前了,时间有些久远,因此今天对之前学习的 RocketMQ 的十一条八股进行复习。

为什么使用消息队列

  • 消息异步;
  • 削峰填谷;
  • 服务解耦;
  • 确保消息的可靠性。

为什么选择 RocketMQ?

RocketMQ 具有低延迟、高吞吐量、高可用性等特点,特别适用于高并发、实时性要求高的场景。

RocketMQ 的优缺点?

优点

  • 高吞吐与低延迟:单机支持 10 万级 TPS 的吞吐量,延迟低至毫秒级;
  • 分布式与高可用:RocketMQ 的可用性非常高,基于分布式架构(RocketMQ 的四个主要部分:NameServer、Broker、Consumer 和 Producer 都可以使用集群模式)方便拓展。
  • 消息可靠性:经过参数优化配置,可以做到消息零丢失。
  • 支持海量消息堆积:RocketMQ 支持 10 亿级别的消息堆积,不会因为消息堆积导致性能下降。
  • 灵活的消息模型:支持发布/订阅、定时/延迟消息、顺序消息、批量消息等。消费者支持 Pull/Push 两种模式,可自定义消费进度管理。
  • 稳定性:阿里出品,经过多次双十一活动的考验,稳定性值得信赖。

总结:高吞吐与低延迟,分布式与高可用,消息可靠,支持海量消息堆积,灵活的消息模型,稳定。

缺点

  • 功能复杂度:事务消息和顺序消息的实现需要开发者理解底层机制,配置不当易引发性能问题;
  • 资源消耗:高并发下对内存和磁盘 I/O 要求比较高,尤其是开启同步刷盘之后,需要权衡可靠性与吞吐;
  • 扩展限制:Topic 数量受限于 NameServer 的元数据管理,过多 Topic 会导致元数据膨胀;
  • 运维成本:需手动管理 Broker 集群和消费者组,自动扩缩容能力较弱。

谈谈你对 RocketMQ 的理解

RocketMQ 是一款可用性高,可靠性强,适用于高并发、高吞吐场景的消息队列中间件,主要由 NameServer、Broker、Producer 和 Consumer 四部分组成。

RocketMQ 的优势在于可用性高、延迟性低、可拓展性强、消息可靠,具有亿级的消息堆积能力,支持灵活的消息模型,包括发布/订阅消息、定时/延迟消息、顺序消息、事务消息等。它尤其适用于电商和金融交易等要求高并发和高可靠的场景。

优点总结:高并发、低延迟、消息可靠、支持亿级消息堆积、支持灵活的消息模型,适用于高并发、高吞吐的电商、金融交易场景。

消息队列有哪些类型?

可以分为队列模型发布-订阅模型

队列模型指的就是 Producer 将消息放在队列中,由 Consumer(可以是 Consumer Group,引入轮询与负载均衡)。一旦 Consumer Group 将消息消费了,消息就不存在了,消费者之间是竞争关系(指的就是消息只能被一个 Consumer 消费,一经消费,消息就不存在了)。队列模型较为简单,是最基本的 MQ 类型,ActiveMQ、RabbitMQ、Redis List 等均属于队列模型。

发布-订阅模型中进一步引入了发布者(Publisher)、订阅者(Subscriber)和主题(Topic)三个概念。发布者负责生产和发送消息,它不直接将消息发送给订阅者,而是发送到消息队列组件(比如 RocketMQ 的 broker)上的主题当中;订阅者根据订阅模型(比如 RocketMQ 的 Pull/Push)从订阅的主题接收消息;主题是消息的管道,发布者将消息发送给特定的主题,订阅者从特定的主题接收消息。

发布-订阅模型可以概括为:订阅者向消息系统注册一个或多个感兴趣的主题,发布者将消息发送到特定的主题,消息系统负责将消息传递给所有订阅了该主题的订阅者,每个订阅者独立接收消息,彼此之间互不影响,也就是说一份消息可以被多次消费

RocketMQ 采用哪种消息队列模型?

RocketMQ 采用发布-订阅模型。

消息的消费模式了解吗?

消息的消费模式分为两种,分别是集群模式广播模式

默认情况下,消费者组采用集群消费,也就是一个 Consumer Group 当中的 Consumer 以竞争的形式消费 Topic 的 Queue 当中的下一条消息,一旦下一条消息被消费,就不会被重复消费了。

广播模式指的是 Topic 下的每一份消息都会发送给 Consumer Group 当中的每一个 Consumer 进行一次消费。

了解 RocketMQ 的基本架构吗?

RocketMQ 的基本架构分为四部分,分别是 NameServer、Broker、Consumer 和 Producer。四者都采用集群模式部署,方便在分布式系统中快速拓展。

详细解释一下 RocketMQ 基本架构中四部分的作用?

NameServer

NameServer 是无状态服务器,角色类似于 Kafka 当中的 Zookeeper。它的特点和作用如下:

  • 每个 NameServer 之间的状态相互独立,无信息交互;
  • 每个 Producer 在发送消息时,都需要先通过 NameServer 得知自己要发送的 Topic 的 Queue 位于哪个 Broker 上;每个 Consumer 会定期从 NameServer 拉取自己感兴趣的 Topic 的 Queue 位于哪个 Broker 上;
  • Broker 在启动时,会将自己的信息发送到所有 NameServer 上,并通过心跳机制确保存活,定时维护 Topic 信息到 Server;
  • 由于 NameServer 节点之间相互独立,Broker 启动时以及进行心跳保活时,需要将自己的状态发送给所有 NameServer。逻辑上来说,NameServer 集群当中的节点最终的状态是一致的

Broker

Broker 扮演消息存储与中转的角色。具体来说,Broker 的职责如下:

  • 消息存储:持久化消息数据;
  • 消息路由:保存 Topic(逻辑存储) 与 Queue(物理存储) 之间的映射关系;
  • 服务提供:响应 Producer 的写入请求与 Consumer 的拉取请求;
  • 高可用保障:通过主从架构实现数据冗余。

单个 Broker 与所有 NameServer 保持长连接,定时将 Topic 信息同步到 NameServer。

Producer

Producer 是 RocketMQ 中负责发送消息的客户端组件,它是消息的源头,可以将业务系统产生的消息发送到 Broker 服务器。具体来说,Producer 有三种消息发送的模式,分别是:

  • 同步发送:等待 Broker 返回确认消息;
  • 异步发送:通过回调函数处理发送消息的结果;
  • 单向发送:不关心发送的结果。

Producer 会自动从 NameServer 获取 Topic 路由信息,从而将 Message 发送到 Topic 所在的 Broker 服务器,因此 Producer 只需要知道 NameServer 的地址即可。

Consumer

Consumer 是 RocketMQ 当中负责接收消息的客户端组件,它从 Broker 中根据 Topic 拉取消息交由业务线程处理,是消息队列中消息的最终目的地。

Consumer 有两种主要的消费模式,分别是:

  • 拉取(Pull)模式:主动从 Broker 拉取感兴趣的 Topic 消息;
  • 推送(Push)模式:Broker 将消息推送给 Consumer。

如何保证消息的可用性/可靠性/不丢失呢?

可以从生产者、中间存储、消费者三个部分来对消息的可靠性进行保障。

生产者
Producer 可以设置请求确认机制,来确保消息被成功存储到 Broker:

  • 同步发送时:注意处理响应结果与异常。得到 Broker 的反馈才算成功发送,否则触发超时重试;
  • 异步发送时:需要在回调函数中处理发送失败的情况,比如通过重试机制确保成功发送;
  • 如果发生响应超时,由于响应超时可能是由于 Broker 消息反馈时产生了网络抖动而导致的,可以通过查询日志的 API 来查看消息是否成功发送到了 Broker,如果没有成功发送则需要重试。

中间存储
存储阶段,可以通过配置可靠性优先的 Broker 来确保消息的可靠性。「控制 Broker 的刷盘频率」

  • 消息只要持久化到了 CommitLog(Broker 底层存储),即使 Broker 宕机,未经消费的消息也不会丢失;
  • Broker 刷盘机制:同步刷盘与异步刷盘。同步刷盘即到来一条消息就进行一次刷盘,显然会提升磁盘 I/O,但是换去了高可靠性。异步刷盘即通过后台 I/O 线程统一刷盘,牺牲了一定的可靠性,换取了刷盘效率;
  • Broker 通过主从模式确保高可用,通过设置 Master Broker 和 Slave Broker 同步复制来确保主从集群的强一致性。

Broker 的主从模式
在这里简要了解一下 RocketMQ 的主从模式。Broker 通过主节点(Master)和从节点(Slave)的协同工作保障服务的可靠性。Master Broker 负责所有的写操作(消息写入、事务处理等),支持读操作。生产者直接讲消息发送到 Master,Master 同步或异步将消息发送给 Slave。Slave Broker 是 Master Broker 的热备份,仅承担读操作,不处理写请求。Master 宕机时,Slave 可升级为 Master。

如何处理消息重复的问题呢?

RocketMQ 可以确保消息的可靠性,但是不能确保消息「准确地仅被消费一次」,因此需要在客户端做好接口的幂等性。常见的幂等性方案包括:

  • 数据库唯一约束(如订单 ID);
  • Redis SETNX 锁(消息 ID 作为 Key);
  • 乐观锁(版本号控制)。

学习 RocketMQ Day3:进阶(二)

怎么处理消息积压?

消费者水平扩容
增加消费者实例的数量,但是需要保证 Consumer Group 中的 Cosnumer 数量 ≤ \leq Queue 数量。「Topic 下有 8 个 Queue 则消费者组最多 8 个实例同时消费。如果消费者实例数 > \gt > Topic 下 Queue 的数量,则多余的实例会闲置」

动态扩容 Queue
临时增加 Topic 的 Queue 的数量。一个可选的方法如下:新建一个 Queue 数量更大的临时 Topic,然后使用转发 Consumer 从当前消息积压的 Topic 将消息转发到新的 Topic,再使用扩容后的 Consumer Group 来对临时 Topic 中的 Message 进行消费。消息积压处理完毕后再恢复正常。

顺序消息如何实现?

  • 局部顺序:仅保证同一业务键(如订单 ID)的消息在同一 Queue 内顺序消费,不同 Queue 之间并行处理。
  • 全局顺序:需将所有消息发送到单个 Queue,比如设置一个 Queue 数量为 1 的 Topic(牺牲并发性能,不推荐在生产环境下使用)。

如何实现消息过滤?

Tag 过滤(最常用)

Tag 是 Message 的二级标签(一级标签是 Topic),生产者可以为消息定义一个 Tag,消费者可以根据 Tag 订阅匹配的消息。基于 Tag 的过滤通常在 Broker 完成,避免不匹配的消息传输到消费者「消费者通过 subscribe 通知 broker 自己感兴趣的 Topic 和 Tag,所以 Broker 知道 Consumer 感兴趣的 Tag」。

基于 Tag 的消息过滤性能较高,可以处理 90% 的场景。但是局限性在于单个消息只能有一个 Tag。

SQL92 表达式过滤(复杂过滤)

生产者可以为 Message 设置自定义属性(Key-Value),消费者通过 SQL 表达式对感兴趣的消息进行过滤(即通过 SQL 对自定义属性进行筛选)。SQL 表达式过滤仍然 Broker 完成,支持数值、布尔、字符串等复杂条件。

SQL 表达式过滤的灵活性比较强,支持 ><LIKEIS NULL 等操作。但是需要 Broker 开启 SQL 过滤功能,且复杂的 SQL 可能增加 Broker CPU 的负载。

消息 Key 过滤

生产者可以为消息配置唯一 Key(比如订单 ID),消费者通过 Key 精准匹配。消息 Key 过滤在消费者端完成(需要先从 Broker 拉取消息),适用于精确查找的场景。

Filter Server

允许用户编写代码并部署到 Broker 的 Filter Server 上,实现完全自定义的过滤逻辑。适用于极复杂的过滤规则。

Filter Server 的灵活性最高,但是复杂性也最高,需要维护 Filter Server,且性能开销大,故生产环境慎用。

延时消息了解吗?

此处引用二哥的 Java 进阶之路的原话:

电商的订单超时自动取消就是一个典型的利用延时消息的例子,用户提交了一个订单,就可以发送一个延时消息,1h 后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

RocketMQ 支持延时消息,只需要在生产消息的时候设置消息的延时级别。但目前 RocketMQ 支持的延时消息级别是有限的:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

RocketMQ 如何实现延时消息?

临时存储 + 定时任务。

在写入阶段,延时消息将会被临时存储到内部 Topic SCHEDULE_TOPIC_XXXX 队列中,队列号对应着延迟级别。原始 Message 的 Topic 将会被写入这条临时存储的延时消息的属性当中(REAL_TOPIC=order_topic)。

在延时到期投递阶段,Broker 启动后台线程 ScheduleMessageService,按延迟级别扫描队列。到达触发时间的消息会被重新写入到原始 Topic 消费队列,供消费者拉取。

怎么实现分布式消息事务?半消息?

RocketMQ 通过事务消息实现分布式事务,其核心是半消息和**两阶段提交(2PC)**的协同工作。

事务消息核心概念

半消息(Half Message)

  • 定义:已经发送到 Broker 但是对 Consumer 不可见的消息称为半消息,存储在 RMQ_SYS_TRANS_HALF_TOPIC 的临时队列中;
  • 作用:确保本地事务执行之前,消息已安全存储,避免生产者宕机导致消息丢失。

事务状态

  • Commit:半消息转为正式消息,投递给消费者;
  • Rollback:丢弃半消息,不投递;
  • Unknown:需要回查本地事务的状态。

基于半消息实现分布式事务的关键步骤

步骤1:发送半消息
生产者发送标记为 TRAN_MSG 的半消息;

步骤2:执行本地事务
生产者实现 TransactionListener 处理本地事务;

步骤3:Broker 处理事务状态

  • Commit/Rollback:如果收到 Commit,则半消息转移到原始 Topic,供消费者消费;否则丢弃半消息。
  • 事务回查:如果生产者未提交状态(可能宕机),Broker 定期回调 checkLocalTransaction 查询事务状态。

介绍一下 RocketMQ 的死信队列

RocketMQ 的死信队列(Dead-Letter Queue,DLQ)是一种用于处理消费失败的容错机制。当消息多次重试后仍无法被成功消费时,会被自动转移到死信队列,避免消息丢失斌便于后续人工干预或分析,修复消息后可以重新发布。

核心作用

  • 存储无法消费的消息:解决因业务逻辑错误、网络问题导致的消息积压;
  • 隔离问题消息:防止失败消息阻塞正常消息的消费;
  • 人工干预入口:支持从死信队列重新投递或修复消息。

触发死信队列的条件

  • 消费重试超限:消息被重复消费超过一定次数仍然失败,会被转入死信队列;
  • 消息属性标记:消费者主动返回 RECONSUME_LATER 状态且重试次数用尽。

如何保证 RocketMQ 的高可用?

RocketMQ 的高可用主要体现在 Broker 采用主从 + 集群的模式。

Broker 主从有两种角色,分别是 Master 和 Slave。Master 负责读写,而 Slave 只读。也就是说 Producer 可以向 Master
写,而 Consumer 可以从 Master 和 Slave 读。通过同步或者异步复制来确保主从一致性。

读的高可用很好理解,Consumer 不需要知道哪个 Broker 是 Master 哪个 Broker 是 Slave,如果某个 Broker 繁忙,会自动转发读请求到其他 Broker。

写的高可用指的是在创建 Topic 时,Topic 自身的多个 Queue 会被分配到若干个 Broker 组上(相同 Broker 名称,不同 BrokerId 构成 Broker 组),当 Broker 组的 Master 不可用时,其他 Broker 组的 Master 仍然可用,就意味着当前 Topic 仍然有 Queue 可以写入,确保了读的高可用。

你可能感兴趣的:(MQ,rocketmq)