01.理解消息系统
可理解为它将应用程序解耦成独立的生产者(寄信人)和消费者(收信人),通过消息队列(邮箱)实现同步/异步通信。消息系统实现了应用程序间的解耦通信,既支持异步的"发后即忘"模式提高吞吐量,也支持同步的"确认机制"保证可靠性。
1.1同步处理
同步模式下,系统必须先确认商品有库存才能允许下单;此后,每个后续步骤都必须阻塞等待前一个步骤完全处理完毕后才能开始。只有当请求顺利通过所有流程、到达最后一个环节时,才能计算出整个订单处理所耗费的总时间。
1.2异步处理
异步模式下,系统在检查库存时可快速获取结果,有则返回,无则生产并放入队列中被消费,无需长时间阻塞等待;各处理步骤能够并发执行,从而显著减少整体等待时间,提升系统吞吐量与性能。那这样处理的时间可以被压缩到50ms处理一个事件。
1.2.1点对点模式
生产者将消息发送到队列中,消费者从中取出消息。消息只能被一个消费者读取,读取成功后即被移除(消息不能被重复处理),例如线程池中的reactor模式。
1.2.2发布-订阅
举个简单的例子,比如游戏里面跨服,并广播今天整体还剩多少把屠龙刀可以暴,然后广播用户暴的屠龙刀的消息。
在发布/订阅模式中,生产者把消息发送到一个主题(Topic),所有订阅该主题的活跃消费者都会收到并处理该消息(广播给订阅者),超过设定时间后被删除。该模式适用于一对多场景,消息广播分发、消费者相互独立。
- Kafka 将消息持久化存储在 Topic 的 Partition 中,每个消费者通过维护自己的偏移量来记录消费位置,不会因被消费而删除。
02.消息队列
2.1流量消峰
当系统短时间内收到大量请求(如秒杀、抢购),直接处理可能导致服务崩溃。消息队列作为缓冲层,将突发的请求暂存到队列中,后端服务按自身处理能力(最大限制数量)匀速消费,从而平滑流量高峰,避免系统过载。
2.2服务解藕
在传统调用中,服务 A 直接调用服务 B、C、D,导致高度耦合。引入消息队列后,服务 A 只需将消息发到队列,无需关心谁来消费;后续新增或修改消费者(如加一个日志服务)不需要改动 A 的代码,降低系统间依赖,提升可维护性和扩展性。
2.3高并发缓冲
Kafka 通过将消息持久化到磁盘日志,天然具备高并发能力。当上游生产速率达到 15,000 条/秒,而下游消费者最大处理能力仅为 10,000 条/秒时,Kafka 会将超出消费能力的消息暂存于磁盘中,形成消费积压(lag)。随后,当生产速率回落至 4,000 条/秒,消费者可逐步追平积压,最终完成全部消息的处理。整个过程无需丢弃数据,实现了对突发流量的削峰填谷。
03.kafka
Kafka 本质上是一个分布式消息队列(Message Queue),使用消息队列的主要优势包括:
- 解耦:生产者和消费者无需彼此感知,可独立扩展或修改各自的处理逻辑,降低系统间依赖。
- 可恢复性:即使消费者进程宕机,消息仍持久化在队列中,待服务恢复后继续处理,避免数据丢失。
- 缓冲:有效应对生产与消费速率不匹配的问题,平滑流量波动。
- 灵活性与峰值处理能力:在突发高负载下,消息队列作为缓冲层,防止核心服务被压垮,提升系统稳定性。
- 异步通信:生产者发送消息后无需等待处理完成,可立即返回,实现非阻塞、高吞吐的异步处理。
3.1消息顺序性
基于Queue消息模型,利用FIFO先进先出的特性,可以保证消息的顺序性。
3.2消息ACK机制
即消息的Ackownledge确认机制,为了保证消息不丢失,消息队列提供了消息Acknowledge机制,即ACK机制,当Consumer确认消息已经被消费处理,发送一个ACK给消息队列,此时消息队列便可以删除这个消息了。如果Consumer宕机/关闭,没有发送ACK,消息队列将认为这个消息没有被处理,会将这个消息重新发送给其他的Consumer重新消费处理。ACK保证了实时性 ,但是牺牲吞吐量。
Kafka 通过消费组实现了灵活的消息分发语义:
- 同消费组内多个消费者: 表现为点对点(Queue)模型(每条消息仅被组内一个消费者处理)
- 不同消费组间:表现为发布/订阅模型(每条消息被每个消费组独立消费一次)
3.3消息持久化
消息持久化(副本),对于一些关键的核心业务来说是非常重要的,启用消息持久化后消息队列宕机重启后,消息可以从持久化存储恢复,消息不丢失,可以继续消费处理。其中可以根据消息的重要程度,设置不一样的处理方式。
3.4消息同步/异步收发
同步:
支持“一问一答”式的同步通信
生产者发送消息后需等待消费者的响应,形成双向应答机制。
例如:张三发信至邮局中转站,并注明回信地址;李四取信处理后,将回执信放回中转站,张三再从中转站取回——整个过程需双方协调完成。
若采用同步拉取(Pull)方式接收消息,当队列为空时,消费者将阻塞等待,直至消息到达或超时。
类似 TCP 的可靠传输机制:若在指定时间内未收到应答,可触发消息重传以保证可靠性。
异步:
- 异步发:生产者发出消息后无需等待确认,可立即继续执行后续操作,提升吞吐效率。
- 异步接:通常采用Push 模式,由消息队列主动将消息推送给消费者,通过回调或事件驱动触发处理逻辑,避免轮询开销。
04.kafka架构
Kafka 由Producer(发消息)、Broker(存消息)、Consumer(消费消息)和(旧版)ZooKeeper(协调)组成。
- 消息按Topic分类,每个 Topic 分为多个Partition(物理日志)。
- 每个 Partition 有 1 个Leader(处理读写)和多个Follower(仅同步,不服务)。
- Consumer Group内消费者分摊 Partition,组间独立消费(实现 Pub/Sub)。
- 消费位置由Offset记录,支持断点续消费。
- 副本保障高可用,但Follower 不提供读服务。
- Kafka ≥3.0 支持去 ZooKeeper(KRaft 模式)。
消息的生产者、消费者
- 消息生产者Producer:发送消息到消息队列。
- 消息消费者Consumer:从消息队列接收消息。
- 被动接受消息:
S-->C - 主动拉取消息:
S<--C通过pull
- 被动接受消息:
在处理队列的数据的时候,我们采用从队列里面pull拉取数据的方式,而不是队列主动push推送数据,考虑每个消费者自身消费情况进行处理,更能保证一个健康的不阻塞的流程。考虑到消费者的处理能力。
4.1工作流程
在 Kafka 中,消息以Topic为单位进行逻辑分类。生产者(Producer)将消息发送到指定的 Topic,消费者(Consumer)从该 Topic 订阅并消费消息——双方面向的是同一个 Topic。
- Topic 是逻辑概念,而Partition(分区)是物理存储单元。
- 每个 Partition 对应一个日志文件(Log Segment),Producer 发送的消息被顺序追加到该日志文件的末尾。
- 每条消息在 Partition 内都有一个唯一的Offset(偏移量),用于标识其位置。
- 每个消费者会独立记录自己消费到的 Offset,以便在发生故障或重启后,能够从上次消费的位置继续处理,实现精确或至少一次的消费语义。
- Kafka 的日志文件默认存储在本地磁盘路径:
/tmp/kafka-logs(可通过log.dirs配置项修改)。
4.2副本原理
- Kafka 中每个分区的副本分为Leader和Follower:创建时选举一个 Leader,其余为 Follower。
- Follower 不对外提供读写服务,仅用于数据同步(不同于 FastDFS、MongoDB 等支持从副本读取的系统)。
- 当 Leader 所在 Broker 宕机,Kafka 通过 ZooKeeper(或 KRaft)感知故障并立即从 Follower 中选举新 Leader;原 Leader 恢复后,自动转为 Follower。
基于leader的副本机制:
Kafka 中每个分区有一个 Leader 副本负责处理所有读写请求,其余副本为 Follower,仅从 Leader 同步数据,不对外提供服务。
- 当 Leader 所在 Broker 宕机时,Kafka 会自动从 Follower 中选举新的 Leader:旧版本依赖 ZooKeeper,新版本(Kafka 3.0+)使用内置的 KRaft 协议。
- 原 Leader 恢复后,会自动降级为 Follower 并同步最新数据。
这种设计保障了高可用与数据持久性,但不支持从 Follower 读取,因此无法通过副本扩展读吞吐。
4.3分区与主题的关系
- 一个分区只能属于一个主题
- 一个主题可以有多个分区
- 同一主题的不同分区内容不一样,每个分区有自己独立的2ffset
- 同一主题不同的分区能够被放置到不同节点的broker
- 分区规则设置得当可以使得同一主题的消息均匀落在不同的分区
05.生产者
5.1topic & partion
Kafka中的消息以主题为单位进行归类,生产者负责将消息发送到特定的主题(发送到Kafka集群中的每一条消息都要指定一个主题),而消费者负责订阅主题并进行消费。可以将topic类比为高速公路,partion类比为多个车道,分区数越多,并行度越高。
topic:
主题(Topic)是 Kafka 中对消息进行逻辑分类的单位,可理解为消息的类别。一个主题含多个分区(多车道)生产者将消息发送到指定topic,消费者订阅感兴趣的topic来接收消息。数据存储在分区而不在主题内部。
partion:
分区(Partition)是 Kafka 中 Topic 的物理分片单元,提供负载均衡,可以理解为kafka把一个大“队列”拆成多个小“队列”,分散到集群中处理。分区可在不同broker上,同一分区内消息有序,整个topic的全局顺序不保证。
每条消息在写入 Kafka 时,只会被分配到 Topic 的某一个 Partition(分区)中,且仅存储一次,不会重复出现在多个分区里。
eg:主题中有4个分区,消息被顺序追加到每个分区日志文件的尾部。Kafka中的分区可以分布在不同的**服务器(broker)**上,也就是说,一个主题可以横跨多个broker。
每条消息在发送到 Broker 前,会根据分区策略(如 Key 哈希)被路由到某个具体分区。合理的分区策略可使消息均匀分布,避免单点 I/O 瓶颈——若一个 Topic 只有一个分区(即一个日志文件),其所在机器将成为性能瓶颈。Kafka 支持在创建 Topic 时指定分区数,也可后续增加(但不能减少),通过扩容分区实现水平扩展。
为了提升容灾能力,Kafka 为每个分区引入多副本机制:同一分区的所有副本保存相同数据(Follower 可能略有滞后),采用“一主多从”架构——只有 Leader 副本处理读写请求,Follower 副本仅同步数据(备份)。所有副本分布在不同 Broker 上,当 Leader 所在 Broker 故障时,系统会从 Follower 中自动选举新 Leader,实现故障转移,保障服务高可用。
eg:在一个 4 节点 Kafka 集群中,若某 Topic 有 3 个分区、副本因子为 3,则每个分区包含 1 个 Leader 和 2 个 Follower,生产者和消费者始终只与 Leader 交互。
5.2分区策略
所谓分区策略是决定生产者将消息发送到哪个分区的算法。Kafka为我们提供了默认的分区策略,同时它也支持你自定义分区策略。
- 轮询策略
- 随机策略
- 按消息键
- 默认分区
5.2.1轮训策略
当Producer 发送消息未指定分区(partition)也未提供 Key时,Kafka 使用轮询策略(Round-Robin)将消息均匀分配到 Topic 的各个分区。eg:一个 Topic 有 3 个分区(P0、P1、P2),则消息依次被分配为:
第1条 → P0,第2条 → P1,第3条 → P2,第4条 → P0,第5条 → P1……以此循环。
5.2.2随机策略
5.2.3按消息键
Kafka 允许为消息指定Key(如用户ID、订单号等)。相同 Key 的消息会被路由到同一个分区,而每个分区内消息是严格有序的,从而保证相同 Key 的消息全局有序。
5.2.4默认分区
消息分配到分区的优先级如下:
- 指定了 Partition→ 直接使用;
- 未指定 Partition 但有 Key→
hash(Key) % 分区数; - 既无 Partition 也无 Key→ 采用轮询(Round-Robin)策略,均匀分配到各分区,实现负载均衡。
06.消费者
传统消息队列有个特点:消息一旦被消费,就会从队列里删掉,而且只能被一个消费者处理。这在某些场景下没问题,但扩展性不好——多个消费者得“抢”同一条消息,没法高效协作。发布/订阅模型虽然允许多个消费者都收到同一条消息,但它也有局限:每个订阅者必须接收主题的全部数据,不能只挑一部分。
这也造成了,当要处理多个主题时,这种“全量订阅”的方式既不灵活,也容易造成资源浪费。所以Kafka引入Consumer Group(消费者组)巧妙地解决了这些问题。
- 一个 Consumer Group 内的多个消费者会自动分摊主题下的不同分区,每人只处理一部分,实现负载均衡;
- 不同 Consumer Group 之间完全独立,哪怕订阅的是同一个主题,彼此也不会干扰;
- 加上 Kafka 本身会把消息在 Broker 上保留一段时间,消费者可以随时重读历史数据。
更妙的是,只靠 Consumer Group 这一个机制,Kafka 就同时支持了两种经典模型, 这样既灵活又可扩展,还避免了传统模型的短板。
- 如果所有消费者都在同一个 Group里,就相当于点对点队列(一条消息只被处理一次);
- 如果每个消费者都在不同的 Group里,就变成了发布/订阅(每条消息广播给所有组)。
6.1消费方式
Kafka 的消费者采用Pull 模式主动从 Broker 拉取消息,这样可以根据自身处理能力控制消费速度(比如忙时少拉,闲时多拉)。
为了避免在无消息时空转,消费者会设置一个timeout参数:当队列为空时,会等待一段时间再返回,而不是立刻轮询,既省资源又及时。
6.2分区分配策略
当消费者组(Consumer Group)内有多个消费者,而 Topic 有多个分区时,Kafka 需要决定“哪个分区由哪个消费者负责”。如果组内消费者数量变化(如扩容/缩容),还会触发“再平衡”重新分配。
Kafka 提供四种分配策略(通过partition.assignment.strategy配置),默认是Range + CooperativeSticky:
6.2.1RangeAssignor 分配策略
按主题独立分配分区:对每个主题单独处理:将分区和消费者按名称排序,用分区数 ÷ 消费者数计算基础分配量,余数部分优先分给字典序靠前的消费者。
- 假设
n = 分区数/消费者数,m = 分区数%消费者数; - 前 m 个消费者各分 n+1 个分区,其余分 n 个。
就是计算得到n,m,其中前面m个消费者分配得到n+1个分区(partion),剩余的消费者分配得到n个分区。
6.2.2RoundRobin分配策略
将所有主题的分区打散,和所有消费者一起按哈希值排序,再轮询分配,更均衡但失去主题局部性。
6.2.3StickyAssignor分配策略
优先保证分配均衡,且在再平衡时尽量保留原有分配(减少数据迁移)。
这样初看上去似乎与采用RoundRobinAssignor分配策略所分配的结果相同,但事实是否真的如此呢?再假设此时消费者C1脱离了消费组,那么消费组就会执行再均衡操作,进而消费分区会重新分配。如果采用RoundRobinAssignor分配策略,那么此时的分配结果如下:RB..会重新轮训分配。
而StickyAssignor的方式会尽量减少变动,在原来的基础上,先给消费者消息较少的一方先分配。
07.数据可靠性保证
为保证 Producer 发送的数据,能可靠发送到指定的topic,topic 的每个 Partition收到Producer发送的数据后,都需要向Producer发送ACK(ACKnowledge确认收到)。ACK 是由Leader 副本返回的,Follower 同步是后台异步进行的。
7.1副本数据同步策略
如果要求全部 Follower 同步完成后才发 ACK(即acks=all+ 等所有副本),虽然能保证强一致性,但是:
- 延迟高;
- 若某个 Follower 宕机或网络卡顿,Leader 会无限等待,导致 Producer 阻塞。
在 Kafka 中,ACK 并非等“所有 Follower”同步完成才发送,而是基于一个动态维护的ISR(In-Sync Replicas)集合来决定。
7.1.1ISR 是什么?
ISR 是与 Leader 保持同步的副本集合(包括 Leader 自身)。Follower 只要能在replica.lag.time.max.ms(默认 30 秒)内跟上 Leader 的日志进度,就留在 ISR 中;否则被踢出。
7.1.2何时发送 ACK?
当 Producer 设置acks=all时,Leader 只需等待 ISR 中的所有副本都写入成功,即可返回 ACK——不是所有配置的副本,而是当前“健康”的 ISR 副本。
7.1.3故障容错与选举
- Leader 宕机后,新 Leader 必须从 ISR 中选举,确保不丢失已确认的消息;
- 因此,只要 ISR 中有至少一个 Follower,就能安全切换;
- 若 ISR 缩减为仅 Leader(如其他副本全掉线),则:
- 默认(
unclean.leader.election.enable=false):拒绝选举非 ISR 副本为 Leader,宁可不可用也不丢数据; - 若开启 unclean 选举,则可能丢数据,但提升可用性。
- 默认(
| 策略 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 半数以上同步(如 Raft) | 多数派确认即ACK | 延迟低,容忍 n 故障需 2n+1 节点 | Kafka未采用 |
| ISR 全部同步(Kafka 方案) | ISR 内所有副本同步才ACK | 强一致性 + 自动剔除慢节点 | 若 ISR 缩小,可用性下降 |
💡 Kafka 没有采用“半数以上”模型,而是通过动态 ISR +
acks=all实现:让 Leader 只等待“健康且同步及时”的 Follower 完成写入后才发送 ACK,既避免因个别副本故障导致系统阻塞,又确保故障切换时不丢失已确认数据。
7.2ACK应答机制
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的Follower全部接受成功。所以Kafka为用户提供了三种可靠性级别,用户根据可靠性和延迟的要求进行权衡,选择以下的配置。
- 当
acks=1(默认):Leader 副本写入成功即返回 ACK,Producer 收到后继续发送下一条; - 若未收到 ACK(如超时或失败),Producer 会自动重试发送(需开启重试机制);
- 更高可靠性可设
acks=all,要求Leader + 所有 ISR Follower 均写入成功才返回 ACK。
tips:ACK 是由Leader 副本返回的,不是每个 Partition 主动“向 Producer 发送 ACK”;Follower 同步是后台异步进行的。
7.3可靠性指标
当然可以。以下是对您提供内容的精炼、准确、语言自然的整理,去除冗余,突出 Kafka 可靠性设计的关键点:
2.6.3 可靠性指标
没有任何系统能做到 100% 可靠,Kafka 的可靠性目标是通过合理配置,无限接近“五个 9”(99.999%)。Kafka 从生产、存储、消费三个环节保障高可靠:
副本机制
- 每个分区可配置多个副本(通常3 副本即可满足大多数高可靠场景);
- 副本分布在不同 Broker 上,避免单点故障;
- 注意:增加分区数不等于提升可靠性,副本数才是关键,过多分区反而带来调度和管理开销。
ACKS
- 设置
acks=all(或-1),确保消息被Leader 和所有 ISR 副本成功写入后才确认; - 配合重试机制(
retries > 0)和幂等性(enable.idempotence=true),避免丢失或重复。
- 设置
消费者 Offset 提交策略
- 默认
enable.auto.commit=true会定期自动提交 offset,存在消息丢失或重复风险; - 高可靠场景应关闭自动提交,在消息处理成功后再手动提交 offset;
- 宁可重复消费,也不因提前提交 offset 导致消息丢失。
- 默认