一、消息队列的基本概念
1.1 什么是消息队列
消息队列(Message Queue,简称MQ),本质上就是一个队列 ,遵循FIFO(First In First Out)先入先出原则。只不过队列中存放的内容是Message(消息),因此得名"消息队列"。
消息队列模型:
生产者 消费者
│ ▲
▼ │
[MSG1] ──► [ 消息队列 ] ──► [Consumer]
[MSG2] ──► [ (FIFO) ] ──► [Consumer]
[MSG3] ──► [ ] ──► [Consumer]
写入顺序:MSG1 → MSG2 → MSG3
消费顺序:MSG1 → MSG2 → MSG3
1.2 消息队列的主要用途
| 用途 | 说明 | 典型场景 |
|---|---|---|
| 异步处理 | 将同步调用拆分为异步消息传递,提升系统响应速度 | 用户下单后,订单服务发送消息,库存服务、短信服务异步处理 |
| 削峰填谷 | 缓解瞬时流量压力,平滑处理能力 | 秒杀场景下,订单请求先入队列,后端服务匀速消费 |
| 解耦服务 | 不同服务之间通过消息通信,互不依赖 | 网关接收请求,投递消息,后端服务独立处理 |
| 流量控制 | 通过队列最大长度限制,保护后端服务 | 设置消息队列最大数量,超出则拒绝或等待 |
| 日志服务 | 收集并处理海量日志 | Kafka常用于日志采集、监控上报 |
| 发布订阅 | 同一消息可被多个消费者消费 | 课堂通知发给所有订阅的同学 |
1.3 点对点模型 vs 发布订阅模型
点对点模型(Point-to-Point):
队列
[Producer] ──────► [Queue] ──────► [Consumer]
(消息被消费后消失)
特点:
- 每条消息只能被一个消费者消费
- 消费后队列中消息消失
- 类似打电话,一对一
发布订阅模型(Publish-Subscribe):
Topic
[Producer] ──────► [Topic] ──────► [Consumer A]
│
├─────► [Consumer B]
│
├─────► [Consumer C]
特点:
- 每条消息可被多个消费者重复消费
- 消费者相互独立,互不影响
- 类似广播,发布者和订阅者彼此不认识
二、Kafka概述
2.1 Kafka是什么
Kafka是由Apache软件基金会开发的分布式消息队列系统,本质是一个MQ(Message Queue)。它被广泛应用于日志收集、消息流处理、事件源等场景。
2.2 为什么使用Kafka
| 优势 | 说明 |
|---|---|
| 解耦 | 允许独立扩展或修改生产者和消费者,两边互不影响 |
| 可恢复性 | 消息持久化到磁盘,进程挂掉后重启仍可继续处理 |
| 缓冲 | 解决生产速度和消费速度不一致的问题,起到蓄水池作用 |
| 峰值处理 | 关键组件顶住突发流量压力,不会因过载崩溃 |
| 异步通信 | 消息放入队列后无需立即处理,提升系统响应效率 |
| 高吞吐量 | 支持每秒百万级消息传输 |
三、Kafka核心概念与架构
3.1 核心术语一览
| 术语 | 解释 |
|---|---|
| Broker | Kafka服务器节点,多个Broker可以组成集群 |
| Topic | 消息主题,类似于消息的分类目录 |
| Partition | 分区,Topic物理上的划分,一个Topic可分为多个Partition |
| Replica | 副本,分区的备份,保证高可用 |
| Producer | 消息生产者,负责发送消息到Kafka |
| Consumer | 消息消费者,从Kafka拉取消息 |
| Consumer Group | 消费组,多个消费者组成,共同消费一个Topic |
| Offset | 消息偏移量,每个Partition中消息的唯一序号 |
3.2 Kafka集群架构图
Kafka集群
=========
[Broker 1] [Broker 2] [Broker 3]
┌─────────┐ ┌─────────┐ ┌─────────┐
│Topic-A │ │Topic-A │ │Topic-A │
│Part-0(L)│◄──副本────►│Part-1(L)│◄──副本────►│Part-2(L)│
│Part-1(F)│ │Part-2(F)│ │Part-0(F)│
└─────────┘ └─────────┘ └─────────┘
▲ │ │
│ ▼ ▼
[Producer] ─────────────► 写入Leader副本 ────────────────
│
Leader副本分布在不同Broker上
Follower副本实时同步
说明:
- L = Leader(主副本),F = Follower(从副本)
- Kafka只往Leader副本写入数据,也只从Leader读取数据
- 只有Leader失效时才会重新选举
3.3 Topic与Partition的关系
Topic: 订单消息
├── Partition 0 ──► [MSG_A] [MSG_B] [MSG_E] Offset: 0, 1, 2
├── Partition 1 ──► [MSG_C] [MSG_D] [MSG_F] Offset: 0, 1, 2
└── Partition 2 ──► [MSG_G] [MSG_H] Offset: 0, 1
分区策略示例(轮询):
MSG_A → Partition 0
MSG_B → Partition 1
MSG_C → Partition 2
MSG_D → Partition 0
MSG_E → Partition 1
...
特点:
- 分区数量决定了并行消费的能力
- 分区越多,吞吐率越高(但并非越多越好)
- 不同Partition之间无法保证消息顺序
- 同一Partition内保证FIFO顺序
3.4 分区副本机制
副本分布示例(3个Broker,3副本):
Topic: 订单消息, Partitions: 3, ReplicationFactor: 3
Partition 0: Leader@Broker1 Follower@Broker2 Follower@Broker3
Partition 1: Leader@Broker2 Follower@Broker3 Follower@Broker1
Partition 2: Leader@Broker3 Follower@Broker1 Follower@Broker2
写入流程:
1. Producer发送消息到Partition 0
2. 消息写入Broker1的Leader副本
3. Broker2、Broker3的Follower副本异步同步
4. Leader确认写入完成,返回ACK给Producer
选举机制:
- Leader失效 → 从该Partition的ISR(In-Sync Replicas)中选举新Leader
- Kafka提供多种ACK策略平衡可靠性与性能
3.5 消息的Offset机制
Partition内部消息存储结构:
Offset: 0 1 2 3 4 5
┌───────┬───────┬───────┬───────┬───────┬───────┐
│ MSG_A │ MSG_B │ MSG_C │ MSG_D │ MSG_E │ MSG_F │
└───────┴───────┴───────┴───────┴───────┴───────┘
▲
Consumer下次从这里消费
Consumer Group A 消费进度:
Partition 0: committed_offset = 3 (已消费到Offset=2)
Partition 1: committed_offset = 5 (已消费到Offset=4)
Partition 2: committed_offset = 1 (已消费到Offset=0)
注意:
- 每个Partition有独立的Offset计数
- 不同Partition之间无法保证全局消息顺序
- 只能保证单个Partition内消息有序
四、Kafka分区分配策略
4.1 四种分区分配策略一览
| 策略 | 原理 | 特点 |
|---|---|---|
| Range | 按主题分区数除以消费者数,分配给每个消费者连续的分区 | 每个主题独立计算,多主题易不均 |
| RoundRobin | 将所有分区轮询分配给消费者 | 跨主题均匀分布 |
| Sticky | 首次分配均匀,Rebalance时尽量少移动分区 | 减少Rebalance开销 |
| CooperativeSticky | 增量协作式Rebalance,渐进式转移分区 | 最小化消息处理中断 |
4.2 Range(范围策略)
原理:
将每个主题的分区按编号排序,然后按消费者字典序排序。计算每个消费者应分配的分区数 = 分区总数 / 消费者数量(向下取整),剩余未分配的分区分给排在前面的消费者。
执行流程:
场景:
- Topic: orders,共有6个分区 [P0, P1, P2, P3, P4, P5]
- Consumer Group: [Consumer-A, Consumer-B](2个消费者)
- 订阅主题:orders
计算过程:
1. 分区按编号排序:[P0, P1, P2, P3, P4, P5]
2. 消费者按字典序排序:[Consumer-A, Consumer-B]
3. 每消费者分配数 = 6 / 2 = 3(向下取整)
分配结果:
- Consumer-A: P0, P1, P2(连续)
- Consumer-B: P3, P4, P5(连续)
多主题场景下的缺陷:
场景:
- Topic-A: 3个分区 [P0, P1, P2]
- Topic-B: 3个分区 [P0, P1, P2]
- Consumer Group: [Consumer-A, Consumer-B]
Topic-A分配:
- Consumer-A: P0, P1
- Consumer-B: P2
Topic-B分配:
- Consumer-A: P0, P1 ← 每个主题独立计算,容易出现不均
- Consumer-B: P2
结果:Consumer-A拿到了4个分区,Consumer-B只拿了2个
特点总结:
| 维度 | 描述 |
|---|---|
| 分配方式 | 每个主题独立计算,不跨主题均衡 |
| 优点 | 实现简单,理解直观 |
| 缺点 | 多主题下容易造成分区不均 |
| 适用场景 | 单一主题或主题数量少的场景 |
4.3 RoundRobin(轮询策略)
原理:
将所有主题的所有分区和所有消费者分别排序,然后轮询分配,每次给消费者分配一个分区,直到分配完毕。
执行流程:
场景:
- Topic-A: [A-P0, A-P1, A-P2]
- Topic-B: [B-P0, B-P1, B-P2]
- Consumer Group: [Consumer-A, Consumer-B]
构建分区列表(按主题分区内编号排序):
[A-P0, A-P1, A-P2, B-P0, B-P1, B-P2]
轮询分配:
第1次: A-P0 → Consumer-A
第2次: A-P1 → Consumer-B
第3次: A-P2 → Consumer-A
第4次: B-P0 → Consumer-B
第5次: B-P1 → Consumer-A
第6次: B-P2 → Consumer-B
分配结果:
- Consumer-A: A-P0, A-P2, B-P1
- Consumer-B: A-P1, B-P0, B-P2
分区数相等,每个消费者3个,整体均匀
特点总结:
| 维度 | 描述 |
|---|---|
| 分配方式 | 跨所有主题分区轮询分配 |
| 优点 | 多主题场景下分配更均匀 |
| 缺点 | 可能打断同一主题分区的连续性 |
| 适用场景 | 消费者数量多、主题数量多的场景 |
4.4 Sticky(粘性策略)
原理:
在保证分区均匀分配的前提下,尽量保持原有的分配关系不变。首次分配时和RoundRobin类似,但发生Rebalance时,会尽量少移动分区,减少Rebalance开销。
初次分配表现:
分区列表:[A-P0, A-P1, A-P2, B-P0, B-P1, B-P2]
消费者:[Consumer-A, Consumer-B]
初次分配(尽量均匀):
- Consumer-A: A-P0, A-P1, B-P0 ← 尝试保持"粘性"
- Consumer-B: A-P2, B-P1, B-P2
Rebalance时的优势:
原分配(3个消费者):
- Consumer-A: P0, P1, P2
- Consumer-B: P3, P4, P5
- Consumer-C: (不存在)
新消费者Consumer-D加入,发生Rebalance:
非粘性策略(Range/RoundRobin):
- Consumer-A: P0, P1
- Consumer-B: P2, P3
- Consumer-C: P4, P5
- Consumer-D: (新加入,需要接管)
(所有分区几乎都要重新分配)
粘性策略:
- Consumer-A: P0, P1, P2 ← 保持不变
- Consumer-B: P3, P4 ← 释放P5
- Consumer-C: (保持不变)
- Consumer-D: P5 ← 只接管P5这一个
重分配开销对比:粘性策略移动更少分区
特点总结:
| 维度 | 描述 |
|---|---|
| 分配方式 | 尽量保持原有分配关系,最小化分区移动 |
| 优点 | Rebalance时减少分区重分配范围,降低消息处理中断 |
| 缺点 | 实现复杂,分配结果不一定完全均匀 |
| 适用场景 | 生产环境首选,减少Rebalance影响 |
4.5 CooperativeSticky(协作粘性策略)
原理:
是Sticky策略的改进版本,采用增量协作式Rebalance。发生Rebalance时,不会一次性收回所有分区,而是渐进式地转移分区给新加入的消费者,对正在处理的消息影响最小。
执行流程:
场景:
- 原消费者:[Consumer-A, Consumer-B]
- 新加入:Consumer-C
初始状态:
- Consumer-A: P0, P1, P2
- Consumer-B: P3, P4, P5
Rebalance第一阶段(增量迁移):
- Consumer-A: P0, P1 ← 释放P2
- Consumer-B: P3, P4, P5
- Consumer-C: P2 ← 渐进接管,不中断正在处理的消息
Rebalance第二阶段(继续调整):
- Consumer-A: P0, P1
- Consumer-B: P3, P4
- Consumer-C: P2, P5 ← 再次接管P5
最终:
- Consumer-A: P0, P1
- Consumer-B: P3, P4
- Consumer-C: P2, P5
对比普通Sticky的Rebalance:
普通Sticky(Eager Rebalance):
- 所有消费者同时释放所有分区
- 所有分区重新分配
- 处理中的消息被中断,需重新处理
CooperativeSticky(增量协作):
- 消费者只释放部分分区
- 分区渐进式转移
- 处理中的消息继续完成,新消息开始使用新分配
特点总结:
| 维度 | 描述 |
|---|---|
| 分配方式 | 增量协作式Rebalance,渐进式转移分区 |
| 优点 | 最小化Rebalance对消息处理的影响 |
| 缺点 | 实现最复杂,收敛时间可能较长 |
| 适用场景 | 对消息处理连续性要求高的生产环境 |
4.6 四种策略对比总结
| 策略 | 分配范围 | Rebalance行为 | 均匀性 | 生产推荐度 |
|---|---|---|---|---|
| Range | 单主题独立计算 | 全量重分配 | 单主题内均匀,多主题可能不均 | 一般 |
| RoundRobin | 全部分区轮询 | 全量重分配 | 整体较均匀 | 常用 |
| Sticky | 尽量保持原分配 | 最小化移动 | 基本均匀 | 推荐 |
| CooperativeSticky | 增量协作 | 分区渐进转移 | 基本均匀 | 最佳 |
4.7 实际配置方式
properties
# Kafka 2.4+ 版本配置
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
# 可以配置多个策略,Kafka会按顺序尝试
partition.assignment.strategy=[org.apache.kafka.clients.consumer.CooperativeStickyAssignor,
org.apache.kafka.clients.consumer.RoundRobinAssignor]
五、Kafka消费模式
5.1 消费组的水平扩展
场景:队列积压10万消息,单消费者1秒处理1万条
单消费者模式:
[Queue] ──► [Consumer] ──► 1万条/秒,积压10秒
扩展为10消费者:
[Queue] ──► [Consumer A] ──► 1万条/秒
[Consumer B] ──► 1万条/秒
[Consumer C] ──► 1万条/秒
...
[Consumer J] ──► 1万条/秒
─────────────────
合计:10万条/秒,实时处理
优势:
- 消费组内消费者可水平扩展
- 新增消费者自动触发Rebalance
- 类似线程池模型,灵活伸缩
5.2 同步 vs 异步发送
同步发送:
Producer ──► 发送消息 ──► 等待ACK ──► 收到确认 ──► 发送下一条
特点:
- 每条消息等待上一条ACK后才发送
- 可靠性高,性能低
- 类似于请求-应答模式
异步发送:
Producer ──► 发送消息 ──► [回调队列] ──► 继续发送下一条
│
后台线程处理ACK
特点:
- 不等待ACK立即返回
- 通过回调函数处理发送结果
- 性能高,可能丢失少量消息
六、消息队列关键特性
6.1 消息可靠性保障
| 环节 | 保障机制 |
|---|---|
| 持久化 | 消息写入磁盘,不依赖内存 |
| 副本 | 多副本冗余存储,Leader失效后自动选举 |
| ACK确认 | Producer发送后可选择ACK策略(0/1/all) |
| 消费确认 | Consumer处理完毕后提交Offset |
ACK策略对比:
| ACK策略 | 说明 | 可靠性 | 性能 |
|---|---|---|---|
| acks=0 | Producer不等任何回复就认为成功 | 低,可能丢消息 | 最高 |
| acks=1 | 等待Leader副本写入完成 | 中,Leader失效可能丢消息 | 中 |
| acks=all | 等待所有ISR副本写入完成 | 高,几乎不丢消息 | 低 |
6.2 一致性策略
| 类型 | 说明 | 副本策略 | 特点 |
|---|---|---|---|
| 弱一致性 | 写入主副本即返回成功 | 只需主副本 | 性能高,可能丢数据 |
| 强一致性 | 所有副本都写入才返回成功 | 需要全部副本 | 可靠性高,性能较低 |
| FastDFS弱一致性 | 同步给从副本但不等确认 | 部分同步 | 平衡性能和可靠性 |
6.3 消息消费的顺序性
| 场景 | 能否保证顺序 | 说明 |
|---|---|---|
| 单Partition单Consumer | 保证顺序 | FIFO |
| 单Partition多Consumer | 不保证顺序 | 并行消费打乱顺序 |
| 多Partition | 不保证全局顺序 | 只保证Partition内有序 |
七、面试追问FAQ
| 问题 | 回答要点 |
|---|---|
| Kafka为什么这么快? | 顺序写磁盘、页缓存、零拷贝技术、批量处理、压缩消息 |
| 如何保证消息不丢失? | 设置acks=all、副本数>=3、消费者手动提交Offset |
| 如何保证消息不重复消费? | 消费端做幂等性处理(如Redis去重、数据库唯一键) |
| 如何保证消息顺序? | 使用单Partition,或在消费端做消息排序 |
| Consumer Group和Partition什么关系? | 分区数>=消费者数,每个分区只能被一个消费者消费 |
| Rebalance什么时候发生? | 消费者加入或离开、订阅的Topic变化、分区数变化 |
| 如何解决热点Partition问题? | 使用消息键(Key)分散到不同分区、合理设计Partition数 |
| Kafka和RabbitMQ区别? | Kafka高吞吐量适合大数据,RabbitMQ功能丰富适合业务消息 |
八、相关消息队列对比
| 特性 | Kafka | RabbitMQ | ActiveMQ |
|---|---|---|---|
| 吞吐量 | 百万级/秒 | 万级/秒 | 千级/秒 |
| 延迟 | 毫秒级 | 微妙级 | 毫秒级 |
| 消息持久化 | 支持 | 支持 | 支持 |
| 消息事务 | 支持 | 支持 | 支持 |
| 负载均衡 | 分区+副本 | 主从 | 主从 |
| 集群 | 原生支持 | 插件支持 | 插件支持 |
| 适用场景 | 日志、大数据 | 业务消息 | 小型系统 |
| 文档 | 丰富 | 丰富 | 一般 |
九、总结
| 维度 | 内容 |
|---|---|
| 本质 | 分布式消息队列,支持发布订阅和点对点两种模型 |
| 核心优势 | 高吞吐量、分布式持久化、水平扩展、多副本高可用 |
| 关键概念 | Broker、Topic、Partition、Replica、Producer、Consumer、Offset |
| 分区策略 | Range、RoundRobin、Sticky、CooperativeSticky |
| 可靠性 | 副本机制 + ACK确认 + 消费者Offset提交 |
| 顺序性 | 只保证单Partition内有序,多Partition需业务层处理 |
| 性能优化 | 批量发送、压缩消息、合理设置分区数、消费者水平扩展 |
根据零声教育教学写作https://github .com/0voice