目录

  • 概述
  • Kafka
  • RocketMQ
  • 常见问题

一、概述

使用场景

  • 解耦:生产者 / 消费者只负责发送 / 消费消息,而不关心对方状态;消费者挂掉后,消息只会积压在队列中,不影响整体业务。
  • 削峰:高并发场景下,把大量请求写入 MQ,消费者按自己的处理能力拉取,避免直接冲垮数据库或服务。
  • 异步:将无需同步完成的步骤放到 MQ 异步处理,提升接口响应速度。

各 MQ 对比

  • Kafka:性能高,分区有序,采用 sendfile。
  • RocketMQ:有序,采用 mmap(需要读取消息内容实现死信队列等)。

消息模型

  • 队列模型:一个消息只能被一个消费者消费,未被消费的消息保持在队列中,直到被消费或超时。
  • 发布-订阅模型:消息发送到 Topic,会被所有订阅 Topic 的消费者消费。

二、Kafka

核心概念

  • Broker:Kafka 集群中的每一个实例。
  • Partition:每个 Topic 有多个逻辑分区,分布在多个 Broker 上实现并发能力。
  • Replica:每个分区有 Leader 和 Follower 两种副本,分布在不同 Broker 上。
  • Leader:生产者和消费者只与这个唯一的 Leader 副本交互。
  • Follower:Leader 故障时,会从 Follower 中选举出一个 Leader。
  • Consumer Group:消费者组,共同消费同一个 topic,每个分区同一时间只能由一个消费者读取,故消费者数量与分区数相同时,吞吐量最大。

消息参数

topic、partition、key(相同 key 的消息进入同一 partition)、data

持久化

先写到内存的 PageCache 中,再由操作系统决定什么时候刷盘(默认),可以设置 log.flush.interval.messageslog.flush.interval.ms 参数,控制日志达多少条、或保留时间超过多少时强制刷盘。

死信队列

Kafka 原生不支持死信队列,因为设计理念专注于消息存储和传递,把消费失败的处理交给上层应用来实现,但是可以通过一些框架实现。

Spring Kafka:在 @KafkaListener 同类下创建 @DltHandler 注解的方法,自动将消息内容、topic、partition、offset 以及异常信息等写入 .DLT 的 topic 中。注意,死信队列本身依赖 Kafka 的可用性,只能作为一道保险,最好增加持久化兜底。


三、RocketMQ

核心概念

  • Broker:并非 Kafka 中的独立服务实例,而是有主从之分;与 Topic 多对多。
  • NameServer:注册 Broker 信息,实现 Broker 管理和路由。
  • Queue:每个 Topic 拆分成多个 Queue 供读写,消息在 Queue 内有序。

持久化

设置 Broker 的 FlushDiskType 参数:

  • 同步刷盘(SYNC_FLUSH):需等待刷盘 ack,安全性高,但性能较低。
  • 异步刷盘(ASYNC_FLUSH):后台异步线程提交,Broker 宕机时会丢失数据。

主从同步

  • 同步复制:只有消息同步双写到主从节点上时才返回写入成功。
  • 异步复制:消息写入主节点之后就直接返回写入成功。

    异步复制不影响可靠性,仅作为高可用手段,而可靠性依赖刷盘策略。RocketMQ 不支持自动主从切换,但消费者可以自动切换到从节点进行消费
    单主从架构下,主节点挂掉后整个系统无法生产,可用性差,因此生产环境一般采用多主多从部署。

功能特性

  • 延时消息:将消息放入延时 Topic 队列,通过定时任务检查实现。
  • 顺序消息:保证严格有序,需设置消息组,把消息映射到同一个 Queue。
  • 分布式事务(最终一致性)
    (1)服务 A 发送 Half Message(消费者不可见)。
    (2)服务 A 收到 MQ 响应后,执行本地事务。
    (3)执行成功则发送 commit 到 MQ,消息变为可消费。
    (4)执行失败则发送 rollback 到 MQ,删除半消息。
    (5)如果 MQ 一直未收到服务 A 返回,回调服务 A 回查。
    (6)根据回查结果再次 commitrollback
  • 回溯消费:按照时间维度来回退消费进度。

四、常见问题

重复消费

  • 生产者端:MQ 自身接口保证不会加入重复消息(如缓存已处理的 message_id)。
  • 消费者端:需消费者做幂等,或拉取到消息就响应(会消息丢失,可配合对账)。

消息丢失

  • 生产者端:生产者在 MQ 返回异常时,设置合理的消息重发逻辑。

    Kafka:send() 方法返回 ListenableFuture,为异步操作,除了用 get() 使其变成同步操作外,还可以 addCallback() 为其添加回调。
  • 消息队列:持久化 + 集群副本防止单点故障。

    Kafka:可配置如下参数。

    • acks:默认为 1,Leader 副本写入成功就返回 ack,将其设置为 all 使得 ISR 列表的所有副本写入成功后,生产者才会接收到来自服务器的响应,更加安全,但延迟更高。
    • min.insync.replicas:配合 acks=all 使用,表示至少写入多少个副本。
    • replication.factor:各分区副本数,一般 >=3
  • 消费者端:接收消息并处理后,再响应 MQ,防止消费处理过程中挂掉。

    Kafka:分区中通过 offset 保证消息有序性,消费者拉取到消息后 offset 默认自动提交(enable.auto.commit=true),可能造成消息丢失;而关闭自动提交会造成重复消费,因此需配合幂等使用。

消息积压

生产者生产速度大于消费者消费速度,往往由 Bug 引起,或是消费者没有批量 / 并行消费逻辑。当大量消息持续积压一段时间时,解决方案如下:

  1. 新建 topic,分区是原来的 10 倍。
  2. 创建临时程序,消费原积压消息,轮询写入新创建的分区中。
  3. 临时部署 10 倍的消费者(修复 Bug 后)去消费新分区的消息。
  4. 消费完积压数据后,恢复原有架构。

消息顺序性

在部分场景下(如用户的转账操作顺序)消息顺序性是不能打乱的,这种情况下:

  • 单线程串行处理消息,而非并发处理。
  • Kafka 是分区有序,因此可以改为 1 个 Topic 只对应一个 Partition,或者发送消息时设置同一 key/partition。
  • RocketMQ 是 Queue 有序,因此可以只创建一个 Queue 实现全局有序。
  • 以上两种方式也可以根据哈希取模使得同一用户局部有序。

2025-09-06 八股文·none