目录
- 概述
- 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.messages
和log.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)根据回查结果再次commit
或rollback
。 - 回溯消费:按照时间维度来回退消费进度。
四、常见问题
重复消费
- 生产者端: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 引起,或是消费者没有批量 / 并行消费逻辑。当大量消息持续积压一段时间时,解决方案如下:
- 新建 topic,分区是原来的 10 倍。
- 创建临时程序,消费原积压消息,轮询写入新创建的分区中。
- 临时部署 10 倍的消费者(修复 Bug 后)去消费新分区的消息。
- 消费完积压数据后,恢复原有架构。
消息顺序性
在部分场景下(如用户的转账操作顺序)消息顺序性是不能打乱的,这种情况下:
- 单线程串行处理消息,而非并发处理。
- Kafka 是分区有序,因此可以改为 1 个 Topic 只对应一个 Partition,或者发送消息时设置同一 key/partition。
- RocketMQ 是 Queue 有序,因此可以只创建一个 Queue 实现全局有序。
- 以上两种方式也可以根据哈希取模使得同一用户局部有序。
文章标题:【八股】消息队列
文章作者:nek0peko
文章链接:https://nek0peko.com/index.php/archives/511/
商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,未经站长允许不得对文章文字内容进行修改演绎。
本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证