Kafka
简介
Kafka 是一个分布式流处理平台,可以用来:
🚚 高效地收集、传输、存储、处理 实时数据流。
它最初由 LinkedIn 开发,用于解决海量日志处理的问题,后来开源给 Apache,成为现在广泛应用的数据中枢系统。
Kafka 能做的事
| 能力 | 描述 |
|---|---|
| ✅ 消息队列 | 实现服务解耦,异步通信,类似 RabbitMQ、RocketMQ,但吞吐更高 |
| ✅ 日志采集系统 | 高性能写入、持久化、传输(用于 ELK、日志分析等) |
| ✅ 事件驱动架构 | 系统之间通过“事件”交互,解耦微服务 |
| ✅ 实时数据流处理 | 配合 Flink、Spark 等组件做实时分析 |
| ✅ 数据管道(ETL) | 把数据从业务系统、数据库、日志收集器汇总到数据仓库、Hadoop、ES 等 |
主要应用场景
- 系统解耦 + 异步通信
- 订单系统 → 支付系统 → 发货系统,各系统通过 Kafka 消息传递,不强耦合,失败可容、削峰填谷。
- 大数据日志收集
- 日志 → Kafka → Flink/Spark → HDFS/ES/DB。
- ELK / Graylog / Hadoop 的数据入口常用 Kafka。
- 实时数据分析
- 网站点击流、用户行为分析、金融交易监控、异常检测等。
- Kafka + Flink/Spark 可以实现 毫秒级处理和响应。
- 监控与告警系统
- 各类监控数据汇总到 Kafka,再下发到告警中心、控制面板。
- 数据库变更同步(CDC)
- MySQL binlog → Kafka → 下游数据库 / 缓存 / 索引系统。
- Debezium + Kafka 可实现 跨系统的数据同步。
重要的原因
| 优势 | 解释 |
|---|---|
| ⚡ 高吞吐 | 每秒百万级消息处理,适合大规模应用 |
| 🔁 可持久化 | 消息写磁盘,多副本,支持数据留存几小时、几天甚至更久 |
| 🧱 分布式架构 | 天生支持横向扩展,节点越多性能越高 |
| 🔌 支持实时与离线 | 可用于实时流处理,也适合批量导入 |
| 🔧 容错性强 | Broker 故障不会丢数据,自动重试 |
| 🧩 生态成熟 | 和 Spark、Flink、Hive、ClickHouse 等无缝集成 |
| 👀 可回溯消息 | 可指定 offset 重复消费,用于补数/故障恢复 |
Kafka 是现代数据架构的 核心中间件,让系统能更灵活、可扩展、实时响应世界的变化。
发展历程
Kafka 的诞生与早期发展
- 2010年:Kafka 由 LinkedIn 的工程师 Jay Kreps、Neha Narkhede 和 Jun Rao 开发,最初的目标是为 LinkedIn 提供一个高吞吐量、可扩展的消息队列,支持处理大量的日志数据。Kafka 最初设计的目的是提供一个 分布式日志系统,能够高效地处理大规模的数据流。
- 2011年:Kafka 成为 LinkedIn 内部的数据流系统的核心,支持实时数据流的处理和消费。LinkedIn 开始使用 Kafka 作为其日志和事件数据的 消息传递层。
- 2011年10月:Kafka 项目正式开源,发布了第一个版本。通过开源,Kafka 得到了更广泛的关注和使用,并逐渐发展成流行的消息中间件系统。
Kafka 1.x 版本(2013-2017)
- 2013年10月:Kafka 0.8.0 发布。这个版本是 Kafka 项目的重要里程碑,加入了 消费者组(Consumer Group)的支持,消费者可以协调工作,实现负载均衡和容错。
- Kafka 0.8.1:引入了 Kafka 的多个副本机制,即每个分区数据都有多个副本,用于实现高可用性。
- Kafka 0.9.0:此版本引入了 安全性增强,支持 SSL 加密 和 SASL 认证,标志着 Kafka 在企业级应用中的成熟。
- Kafka 0.10.0:这一版本引入了 消息压缩、Kafka Streams API 和 消费者的高效消费方式,标志着 Kafka 开始作为流处理平台出现。
- Kafka 0.10.2:引入了 消息幂等性,确保生产者发送的消息不会因为网络问题等导致重复发送,提高了消息可靠性。
Kafka 2.x 版本(2018-2021)
Kafka 在 2.x 版本中加入了许多重要的新特性,使其成为大数据流处理和实时数据平台的核心组件。
- Kafka 2.0.0(2018年7月发布):
- KIP-500(Kafka Improvement Proposal 500)引入了 Kafka 的 无 Zookeeper 架构,但这项特性在后续版本逐步实施。
- 引入了 Kafka Streams API 的增强,进一步优化了流处理的功能。
- 支持了更高效的 生产者和消费者协议,提高了性能。
- Kafka 2.1.0(2018年11月发布):
- 该版本引入了 更高效的 Kafka Streams API 和 新的事务性生产者,可以支持 Exactly Once 消息语义(精确一次语义),这使得 Kafka 能够保证 生产者、消费者 和 Kafka Streams 在发生失败时不会导致数据丢失或重复处理。
- Kafka 2.2.0(2019年4月发布):
- 引入了 Kafka Connect 的增强,提供更强大的 数据源 和 数据接收器 扩展能力。
- 增强了 Kafka Streams 的窗口化和性能。
- Kafka 2.3.0(2019年6月发布):
- 引入了 Kafka Streams 的动态线程管理,可以根据流量的变化调整流处理的并发度。
- 支持 Kafka Connect 配置的增量更新,简化了连接器的管理。
- Kafka 2.4.0(2019年11月发布):
- 引入了 Kafka Streams 处理状态管理 的改进和 Kafka Connect 的增量数据处理。
- 完善了 跨数据中心的复制 和 提升了性能。
- Kafka 2.5.0(2020年4月发布):
- 引入了 无 Zookeeper 的元数据管理,即 Kafka 开始在没有 Zookeeper 的情况下存储集群元数据(这项特性在后续版本中进一步完善)。
- 优化了 Kafka 消费者的 分配算法 和 自动均衡。
- Kafka 2.6.0(2020年8月发布):
- 引入了 Kafka Streams 的容错性增强,并增加了对 架构变更 和 数据恢复 的支持。
- 完成了 Kafka 不依赖 Zookeeper 的过渡,逐步实现了 KRaft 模式(Kafka Raft),即在 Kafka 自身管理元数据的模式下运行。
Kafka 2.8.0 及之后版本(2021-至今)
- Kafka 2.8.0(2021年4月发布):
- Kafka 正式进入 KRaft 模式,逐步摆脱 Zookeeper,实现 Kafka 自管理的元数据存储。
- 进一步提高了 事务性支持,对 Exactly Once 语义做了更多优化。
- 加强了 跨数据中心复制 和 消息压缩的性能。
- Kafka 2.8.1(2021年6月发布):
- 解决了 Kafka 集群中的一些 性能问题 和 bug 修复。
- 完善了 无 Zookeeper 模式 下的稳定性和性能。
- Kafka 3.0.0(2021年9月发布):
- 完全去除 Zookeeper,标志着 Kafka 完全过渡到 KRaft 模式,Kafka 集群的元数据管理不再依赖 Zookeeper。
- 引入了更高效的 日志压缩机制,减少了存储开销。
- 加强了 数据存储 的优化,提升了 消息存储的写入和读取性能。
Kafka 的新特性和演变
- 无 Zookeeper 模式(KRaft):
- 早期 Kafka 依赖 Zookeeper 来管理集群的元数据,但随着时间的推移,Kafka 逐步去除了 Zookeeper,采用了 KRaft 模式(Kafka Raft)。这一模式下,Kafka 自身管理集群的元数据,提高了集群的稳定性和简化了操作。
- Exactly Once 语义:
- Kafka 逐步增强了对 Exactly Once 语义的支持,确保消息在生产、消费和流处理过程中不会丢失,也不会重复处理。特别是 2.x 版本引入的 事务性支持,大大增强了 Kafka 作为分布式消息系统的可靠性。
- Kafka Streams 和 ksqlDB:
- Kafka Streams API 提供了一个用于实时流处理的强大框架,能够基于 Kafka 中的数据流执行复杂的实时数据分析。
- ksqlDB 是一个流式 SQL 引擎,提供了更简便的流处理方式,用户可以使用 SQL 来编写 Kafka 数据流处理的查询。
- Kafka Connect:
- Kafka Connect 提供了一个用于连接各种数据源和数据目标的框架,简化了将外部系统与 Kafka 集成的复杂度。它支持高效的增量数据同步,并能与许多外部数据存储(如数据库、HDFS、Elasticsearch)无缝集成。
架构

- Producer:生产者负责将数据发布到 Kafka 中的主题(Topic)。消息会按照某种逻辑(如轮询、键值哈希等)分发到该主题的不同分区,生产者可以发送单个消息或者批量消息到指定的主题。
- Consumer:消费者从主题中的特定分区读取消息。消费者可以是单个实例,也可以组成一个消费者组(Consumer Group)。Kafka 支持多消费者模型,每个消费者组能够独立读取主题中的数据。Kafka 通过消费者组(Consumer Group)管理消费进度,确保消息被所有需要的消费者组消费。
- Broker:Kafka 集群中的每个节点称为一个 Broker。Broker 负责接收、存储和提供来自生产者的消息。一个 Kafka 集群通常由多个 Broker 组成,每个主题的分区会分布在不同的 Broker 上,分布式存储消息,以实现高可用性和容错性。
- Topic:数据在 Kafka 中是通过主题组织的。生产者将数据发送到主题中,消费者从主题中读取数据。主题可以分为多个分区(Partition),分区使得主题能够水平扩展,以提高吞吐量和并行处理能力。
- Partition:每个主题都可以被分为多个分区,分区是 Kafka 中的基本并行单元。每个分区中的消息是有序的,但是跨分区的消息没有全局顺序。通过分区,Kafka 能够在集群中分布负载,并实现高并发的数据处理。
- Zookeeper:Kafka 使用 Zookeeper 来管理集群的元数据、Broker 状态、主题配置等信息。在新的 Kafka 版本中,Zookeeper 被逐渐替代为 Kafka 自带的集群协调功能,但在旧的版本中,Zookeeper 是必需的。
- Leader 和 Follower:每个分区都有一个 Leader 和若干 Follower。Leader 负责处理生产者和消费者的读写请求,而 Follower 只负责同步 Leader 中的数据。当 Leader 失效时,Kafka 通过 Zookeeper 或自带的协调功能选举新的 Leader。
数据倾斜问题
在 Kafka 中,如果一个 Topic 有多个 Partition,但这些 Partition 中的消息数量或流量分布不均衡,就会出现 数据倾斜(Data Skew) 的问题。数据倾斜指的是:
- 某些 Partition 接收到大量消息,而其他 Partition 接收很少甚至没有;
- 导致部分 Kafka Broker 压力过大;
- 消费端负载不均,有的 Consumer 处理不过来,有的却很空闲;
- 严重时会造成消费延迟、系统资源浪费甚至服务不稳定。
数据倾斜的原因:
- 生产者分区策略问题:
- 默认使用 key hash 分区器,如果 key 分布不均,hash 后也会不均;
- 或者显式指定了某些 Partition,使得只有少数 Partition 被使用。
- 没有设置 key:
- Kafka 会使用轮询(Round-Robin)策略分发;
- 如果某些 Producer 实现有问题或批次不均,也可能造成不均衡。
- 动态 topic 扩容不合理:
- 增加 Partition 后老的消息还在旧的几个 Partition 中积压。
数据倾斜带来的问题
- Broker 负载不均衡;
- 消费组中的 Consumer 无法均衡分配 Partition;
- 消息处理延迟拉大;
- 系统资源浪费,甚至可能引起消息堆积、告警、服务不可用。
❗Kafka 遇到数据倾斜时 不会自动触发重平衡,因为:
Kafka 的 重平衡(Rebalance)机制,是针对消费组(Consumer Group)的,不是针对生产端的 Partition 数据分布。
当满足以下条件时,Kafka 的 Consumer Group 会触发重平衡:
- 有新的消费者加入或离开消费组;
- 某个消费者崩溃或断连;
- 订阅的 Topic 改变;
- Kafka Broker 宕机或恢复。
⚠️ 重平衡只是重新分配 Partition 给消费者,不会改变 Partition 中的数据分布。
Kafka 本身并不知道“数据是否分布均匀”或“某个 Partition 太热”——它只是把数据按照 Producer 的分区逻辑写入特定 Partition。
即使某个 Partition 压力特别大,Kafka 也不会主动调整数据写入的 Partition,也不会因为数据倾斜自动调整 Consumer 分配(除非有 Consumer 崩溃等情况)。
Kafka 不会自动解决,你需要:
- 优化生产者分区策略,确保 key 均匀分布或用 Round-Robin;
- 监控各 Partition 的消息量与延迟,定位热 Partition;
- 扩展 Topic 的 Partition,并重新设计 key 分布策略;
- 在消费端使用多线程消费单个 Partition(针对极端倾斜);
- 引入中间层流量调度逻辑(如 Kafka Connect、流处理框架 Flink/Spark Streaming 进行动态 repartition)。
- 自定义分区器 编写 Partition 分配逻辑,使消息均匀地分发到各 Partition。
在 k8s 中增加消费者实例
每个 Pod(在 K8s 中运行的消费者实例)都会作为一个独立的消费者加入到 Kafka 消费者组中。
如果只是将消费者代码在 K8s 中复制多个实例(例如通过增加副本数),每个实例都会作为一个独立的消费者加入到 Kafka 消费者组中,Kafka 会根据消费者组的机制将消息分配给各个消费者实例。这样可以通过增加实例来增加消费者的并发处理能力。
并不需要修改代码来增加消费者组中的消费者实例个数。只要 Kubernetes 中的 Pod 数量增加,Kafka 消费者组的成员数就会增加,Kafka 会自动重新分配分区给新的消费者实例。
消费者组的大小不能超过 Kafka 主题的分区数。如果消费者实例多于分区数,那么有些消费者将没有消息可消费。所以,如果你想要通过增加消费者实例来提高消费能力,最好先确保 Kafka 主题有足够的分区数。
总之,在 K8s 中复制消费者实例是增加 Kafka 消费者组实例的一种有效方式,无需修改代码,Kafka 会自动处理消费者与分区之间的关系。如果希望增加消费者组的消费者实例数,确保在 K8s 中增加副本数即可。
保证消息可靠性
Kafka 保证消息可靠性主要通过以下几个机制来实现,从生产者到消费者的整个链路上都设计了相应的保障措施:
生产者(Producer)端的可靠性
✅ a. acks 参数(确认机制)
acks=0:生产者不等待任何来自服务器的确认,有可能丢消息。acks=1:只要 leader 分区副本写入成功就确认,副本挂掉可能丢数据。acks=all(或-1):所有副本都成功写入才返回确认,最可靠但延迟稍高。
✅ b. 重试机制(retries + retry.backoff.ms)
- 网络异常或临时失败时,自动重试发送消息。
- 默认开启
retries,但要配合幂等性使用避免重复消息。
✅ c. 幂等性(idempotence)
- 开启
enable.idempotence=true后,Kafka 会自动分配唯一Producer ID,确保 即使重试也不会重复写入消息。
Kafka 服务端(Broker)端的可靠性
✅ a. 消息持久化
- Kafka 会将消息先写入 页缓存(page cache),然后定期刷新到磁盘(可配置)。
- 可以配置
log.flush.interval.messages和log.flush.interval.ms控制刷盘频率。
✅ b. 副本机制(Replication)
- 每个 Topic 的 Partition 可以设置多个副本(replication factor)。
- 一个 Partition 有一个 leader 和多个 follower,follower 会实时同步 leader 数据。
✅ c. ISR(In-Sync Replicas)机制
- 只有在 ISR 列表中的 follower 副本 才算同步成功,
acks=all依赖这个。 - leader 崩溃后,会从 ISR 中选一个新的 leader,确保数据不会丢失。
消费者(Consumer)端的可靠性
✅ a. 自动 or 手动提交 offset
- 默认是
enable.auto.commit=true,定期自动提交 offset(可能重复消费)。 - 更可靠的方式是 手动提交 offset,只有在消息处理成功后才提交,防止消息丢失。
✅ b. 消费幂等性
- 消费者要注意幂等处理(比如写数据库要避免重复插入)。
- 通常结合 offset 存储(如:Kafka、数据库、外部存储)来做到“恰好一次”处理。
Exactly Once
如果想实现更高级的“Exactly Once(恰好一次)语义”,Kafka 从 0.11 版本开始支持 事务机制(transactions),但需要搭配幂等生产者 + 手动控制 offset + 支持事务的下游系统(如支持事务的数据库)。
Kafka 中实现 Exactly Once Semantics(EOS,精确一次语义),是为了确保:
每条消息被处理一次且仅一次,既不会丢失,也不会重复消费。
✅ Kafka 实现 Exactly Once 依赖两大核心机制:
幂等性生产(Idempotent Producer)
防止同一条消息因重试而被写入多次。
- 开启方式:
enable.idempotence=true - 保证 单个 producer -> 单个 topic partition 不会因重试导致重复写入。
- 自动开启
acks=all和retries > 0。
事务性写入(Kafka Transactions)
保证 多条消息的原子写入,以及消费 offset 与消息写入绑定在一个事务内。
- 开启方式(Java 示例):
props.put("enable.idempotence", "true"); props.put("transactional.id", "my-tx-id"); producer.initTransactions(); - 使用流程:
producer.beginTransaction(); // 处理消息 & 发送结果 producer.send(...); producer.send(...); // 将消费 offset 也作为事务提交的一部分 producer.sendOffsetsToTransaction(offsets, consumerGroupId); producer.commitTransaction(); // 或 abortTransaction();
🚀 事务提交成功后:
- 所有写入的数据才对消费者可见。
- offset 也一起提交,保证“处理 + offset 提交”是一体的。
✅ Kafka Exactly Once 的关键保障点
| 机制 | 作用 |
|---|---|
| 幂等性生产者(Idempotence) | 避免 producer 重试时重复发送消息 |
| 事务性 producer | 保证一批消息要么都写入,要么都不写 |
| sendOffsetsToTransaction | 把 offset 也纳入事务中,确保“处理一次” |
| read_committed 模式 | 消费者只读取已提交的事务数据 |
transactional.id必须在整个 producer 生命周期中保持唯一。- 事务机制略有性能开销,不适合低延迟、超高吞吐但容忍重复的场景。
- 不能实现跨系统 EOS(比如 Kafka + DB),需要使用二阶段提交或外部协调机制。
Kafka 的 Exactly Once = 幂等 + 事务 + 消费 offset 一体提交 + 只读已提交数据。
offset 提交问题
✅ 自动提交:Kafka 每隔一段时间自动提交 offset
✅ 手动提交:你自己决定什么时候提交 offset(更安全、更灵活)
Kafka 默认使用的是:✅ 自动提交 offset(auto commit)。
enable.auto.commit=true
auto.commit.interval.ms=5000 // 默认每5秒提交一次offset
也就是说,如果不显式关闭,Kafka 会默认:
每隔 5 秒钟自动把最近 poll 到的消息的 offset 提交给 Kafka 的 __consumer_offsets topic。
🚦默认自动提交的行为特点:
- 提交时机不可控:你还没处理完消息,它可能已经提交 offset 了
- 容易导致消息丢失(消息没处理完,下次启动不会再拉)
- 或者导致重复消费(处理完了但没提交成功,导致重拉)
可以在消费者配置中显式加上:
enable.auto.commit=false
然后在代码中使用手动提交方式,比如:
consumer.commitSync(); // 或者 commitAsync()
手动提交开启方式:
enable.auto.commit=false
- ✅
commitSync():同步提交,等 Kafka 返回结果,可靠 - ✅
commitAsync():异步提交,性能好但可能失败 - ✅ 可精确控制 offset:按 partition 分别提交
- 更可靠,只有在你确认处理成功后再提交
- 可以精细控制 offset 提交点
- 可与事务结合(Exactly Once)
✅ Kafka 中既可以由 Consumer 提交 offset,也可以由 Producer 提交 offset,两者适用的场景不同。
| 提交方 | 是否常见 | 使用场景 | 是否参与事务 | 是否支持 Exactly Once |
|---|---|---|---|---|
| ✅ Consumer 提交 offset | 常见 | 普通消息消费(无事务) | ❌ 不参与事务 | ❌ 不能保证 Exactly Once |
| ✅ Producer 提交 offset | 用于事务 | 需要保证“发送 + 提交 offset 一致性” | ✅ 事务提交的一部分 | ✅ 可实现 Exactly Once |
✅ Consumer 自己提交 offset(传统方式)
consumer.commitSync(); // or commitAsync()
- 常见于普通消费场景
- 对幂等性 or Exactly Once 没有严格要求
- 适用于数据处理失败时可以重复消费的业务
缺点:
- offset 提交和业务处理是两个独立步骤
- 中间失败就可能导致:
- 重复消费
- 消息丢失
✅ 二、Producer 提交 offset(事务场景)
producer.sendOffsetsToTransaction(offsets, consumerGroupId);
必须配合:
producer.beginTransaction();
...
producer.commitTransaction();
- 用于 Kafka 的 事务性处理
- 典型场景是“从 A topic 消费 → 处理 → 写入 B topic”
优点:
- 将“处理完 + 消息写出 + offset 提交”绑定成一个原子事务
- 确保“只处理一次,且处理成功才提交 offset”
- 实现 Exactly Once Processing
高低水位
在 Kafka 中,高水位(High Watermark)和低水位(Low Watermark)是与消息的消费进度和数据一致性相关的重要概念。它们与 Kafka 的分区、副本同步和数据的可靠性保障密切相关,确保 Kafka 系统中的消息不会丢失,并且消费者可以按顺序消费消息。
高水位(High Watermark,简称 HWM)
- 高水位是一个分区中所有副本(包括 Leader 和 Follower)都已经同步并且确认的消息偏移量。
- 高水位表示该偏移量之前的消息对所有副本来说都是 已确认 的,也就是说,所有的副本都已经同步了这部分数据。
- 对消费者来说,高水位是它们能够 消费 的最新消息的偏移量。
- 高水位确保了数据的 一致性,只有当高水位更新时,消费者才能消费到新的消息。
低水位(Low Watermark,简称 LWM)
- 低水位指的是分区中所有副本同步进度的 最小偏移量。也就是说,低水位是所有副本的同步进度中最落后的那个副本的偏移量。
- 低水位用来描述 副本同步的延迟,它表示最慢副本可以保证读取到的最新消息的偏移量。
- 低水位主要用于反映系统的 数据同步状态,可以帮助 Kafka 判断哪些副本落后,需要进行数据同步。
✅ 高水位的作用
- 消费者消费的依据:
- 高水位标记了 消费者可以读取的最大偏移量,消费者只能消费 高水位之前 的消息。
- 消费者在消费时,不会消费高水位之后的消息,确保消费数据的一致性和完整性。
- 高水位也决定了 消费的进度,当消费者消费到某个偏移量时,Kafka 会更新这个消费者所在分区的高水位。
- 副本同步的保障:
- 高水位也作为所有副本的同步进度的标记,表示到高水位的消息,所有副本都已经同步并且保证数据一致性。
- 只有当 Leader 的消息已经成功同步到所有 ISR(In-Sync Replicas)中的副本时,才会更新高水位。
- 数据的持久性:
- Kafka 中的高水位表示所有 ISR 中的副本都已经成功复制的数据位置。只有高水位以下的数据被认为是“已提交”的消息,才是可靠的。如果某个副本失效,只要高水位以下的数据已经被其他副本同步,这些数据就不会丢失,因此高水位是 Kafka 保证数据持久性的关键机制。
- 这也确保了 Kafka 对消息的持久性承诺:即使某个副本失败,只要其他副本中有数据,它就不会丢失。
✅ 低水位的作用
- 副本的同步进度:
- 低水位反映了分区中副本同步进度的 最小值,如果某个副本落后于其他副本,它的同步进度就会影响到低水位的更新。
- 当某个副本的日志跟不上其他副本的更新时,低水位就会保持在这个副本的最后同步偏移量。低水位有助于监控 副本的同步延迟。
- 确保一致性:
- Kafka 使用低水位来确保数据的一致性。如果一个副本落后于 Leader 太多,它会被从 ISR(In-Sync Replicas)中移除,这意味着该副本不再与 Leader 保持同步,不再能保证数据的一致性。
- 低水位帮助 Kafka 确保只有 同步的副本 才能成为 Leader,当一个副本被移出 ISR 时,Kafka 会选择其他副本作为 Leader,以避免数据丢失和不一致。
- 判断副本的健康状态:
- Kafka 会根据低水位来判断 副本的健康状态。如果一个副本与 Leader 的同步进度落后太多,它会被认为是 “落后副本”,并且不会参与消息的写入或读取。
Rebalance
Kafka 的 Rebalance(重平衡)机制本质上是一个协调过程,用于在消费者组内动态分配分区,以保证消费任务均匀分布。Rebalance 主要由 Kafka Consumer Group 协议(Group Membership Protocol)驱动,涉及多个关键组件和步骤。
Kafka 的 Rebalance 可能会在以下几种情况发生:
- 新消费者加入(导致需要重新分配分区)
- 已有消费者退出(导致其分配的分区需要重新分配)
- 订阅的主题分区数量发生变化(例如新增分区)
- 消费者心跳超时(消费者被判定为故障,需要重新分配其分区)
- 消费者组的协调器(Group Coordinator)发生故障或迁移(导致消费者组需要重新注册和重新分配分区)
- 某些分区的 leader broker 发生变更,导致分区的元数据变更(例如分区副本切换 leader 或 broker 故障)
Kafka Rebalance 的底层涉及几个关键组件:
- Group Coordinator(组协调器):Kafka 服务器端的一个角色,负责管理消费者组的成员状态,确保分区的正确分配。
- Consumer Group Protocol(消费者组协议):用于管理组成员关系,支持动态成员变更和负载均衡。
- Leader Consumer(组 leader):消费者组中的一个消费者会被选为 leader,负责计算新的分区分配方案。
具体流程
(1) 发现 & 变更检测
当有新的消费者加入或已有消费者离开时,Kafka 服务器端的 Group Coordinator 负责检测到变化,触发 Rebalance 过程。
(2) Group Coordinator 管理消费者组
- 维护每个 Consumer Group 的元数据(如成员列表、订阅主题)。
- 确保每个 Consumer 维持定期的心跳,否则认为它已失效。
(3) 进入 Rebalance 状态
- 组成员都进入
Rebalance in progress状态。 - Kafka 会暂停所有正在进行的消费任务,释放现有的分区分配关系。
(4) 选举 Leader Consumer
- Group Coordinator 选择一个消费者作为 leader(通常是最早加入的消费者)。
- Leader 负责计算新的分区分配方案。
(5) Leader 计算分区分配方案
- RangeAssignor:按范围分配,每个消费者分配连续的分区(默认)。
- RoundRobinAssignor:循环分配,均衡分布分区。
- StickyAssignor:尽量保持原有分区分配,减少变更。
(6) 分区分配方案提交
- Leader 计算完成后,将分配方案提交给 Group Coordinator。
- Group Coordinator 确认并通知所有消费者。
(7) 消费者收到分区分配
- 每个消费者获取新的分区分配信息。
- 重新建立连接,恢复消费。
Rebalance 的优化
Kafka 在 2.3 版本后引入了 Incremental Cooperative Rebalancing(增量协作 Rebalance),优化了传统 Rebalance 过程:
- 旧的 Eager Rebalance 方式会在重新分配时导致短暂消费中断(所有分区都被撤销)。
- Incremental Rebalance 允许消费者在新的 Rebalance 过程中逐步增加或减少分区,避免大规模分区迁移。
Rebalance 相关的关键参数
在 Kafka 消费者配置中,有几个参数影响 Rebalance 行为:
session.timeout.ms:消费者心跳超时时间,超时会触发 Rebalance(默认 45s)。heartbeat.interval.ms:消费者向 Group Coordinator 发送心跳的间隔(默认 3s)。max.poll.interval.ms:消费者调用poll()的最大间隔,超时会触发 Rebalance(默认 5min)。partition.assignment.strategy:控制分区分配策略(默认RangeAssignor)。
优化 Rebalance
- 调整心跳参数:增加 heartbeat.interval.ms 和 session.timeout.ms 的值,避免消费者因网络抖动被误判为失联。
- 使用静态成员 ID:设置 group.instance.id,避免消费者断开连接时触发 rebalance。
- 减少动态订阅变化:避免频繁更改订阅主题或调整分区。
- 使用分区分配策略:自定义分区分配策略,优化分区分配逻辑,减少分区迁移。
零拷贝
零拷贝(Zero Copy) 是一种优化技术,主要用于减少数据在用户空间和内核空间之间的拷贝次数,从而提升性能。它广泛应用于高性能网络编程、大文件传输和数据库系统中。
传统数据传输(例如文件发送到网络)的流程通常如下:
- 文件读取: 从磁盘读取文件到内核缓冲区。
- 数据拷贝: 将内核缓冲区的数据拷贝到用户空间缓冲区。
- 数据处理: 用户进程可能对数据进行处理。
- 数据发送: 将用户空间缓冲区的数据再次拷贝到内核缓冲区,通过网卡发送出去。
上述流程中,数据需要在 用户空间 和 内核空间 之间多次拷贝,造成以下问题:
- CPU 消耗大: 每次拷贝都需要 CPU 参与。
- 内存带宽浪费: 数据被重复拷贝,占用大量内存带宽。
零拷贝 是指在数据传输过程中,通过避免数据在用户空间和内核空间之间的冗余拷贝,从而减少 CPU 和内存带宽的开销。
它并不是完全没有拷贝,而是通过减少拷贝次数达到“接近零拷贝”的效果。
实现零拷贝的技术手段
- sendfile 系统调用
- sendfile 是 Linux 提供的系统调用,常用于将文件数据直接从磁盘传输到网络,而无需经过用户空间。
- 文件数据从磁盘读取到内核缓冲区。
- 内核将数据直接从内核缓冲区复制到网络协议栈的缓冲区。
- 网络协议栈将数据通过网卡发送。
- 这样数据在整个过程中不需要进入用户空间。
- 避免了用户空间和内核空间之间的拷贝。
- 减少了上下文切换。
- mmap + write
- mmap 是将文件映射到用户进程的虚拟地址空间,结合 write 可以减少一次数据拷贝。
- 文件通过 mmap 映射到用户空间,与内核缓冲区共享内存。
- 使用 write 将内核缓冲区的数据直接发送到网络。
- 这种方法减少了一次从内核到用户空间的拷贝。
- Direct I/O
- 通过绕过内核的页缓存,直接将数据从磁盘读取到网络协议栈的缓冲区,避免了内核缓冲区和用户空间缓冲区的拷贝。
- 适用场景:高性能文件传输,例如 Nginx 和 Kafka 中广泛使用。
- DMA(Direct Memory Access)
- 现代硬件支持 DMA,允许直接在硬件和内存之间进行数据传输,避免 CPU 参与拷贝操作。
- 在网络传输中,网卡使用 DMA 将数据从内存读取并发送到网络。
优点
- 性能提升:
- 减少了 CPU 消耗和内存带宽占用。
- 提高了数据传输的吞吐量。
- 减少上下文切换:
- 用户进程和内核进程之间的切换显著减少。
- 降低延迟:
- 数据处理路径更短。
零拷贝的限制
- 硬件依赖:
- 零拷贝技术需要操作系统和硬件支持(如 DMA、sendfile)。
- 适用场景有限:
- 对于需要频繁修改或处理数据的场景,零拷贝的优势较小。
- 内存管理复杂:
- 零拷贝可能带来复杂的内存管理问题,如缓冲区回收。
Kafka vs RocketMQ
| 特性 | Kafka | RocketMQ |
|---|---|---|
| 开发语言 | 主要用 Java,但支持多种客户端语言 | 主要用 Java,支持多种客户端语言 |
| 消息模型 | 发布/订阅和队列模型 | 发布/订阅和队列模型 |
| 消息存储 | 默认是基于磁盘的持久化存储,支持日志存储机制 | 基于文件存储,提供高效的消息存储和索引机制 |
| 消息顺序 | 支持消息顺序,但只限于同一分区内的消息 | 支持全局顺序或按照分区顺序来保持消息顺序 |
| 消息消费 | 支持 多消费者,支持消费者组 | 支持 多消费者,支持消费者组 |
| 高可用性 | 支持分布式架构、分区和副本,容错性高 | 支持分布式架构、分区和副本,容错性高 |
| 吞吐量 | 高吞吐量,能处理大量的消息流量 | 适合低延迟、高并发场景,吞吐量也较高 |
| 消息确认 | 支持 至少一次、最多一次、精确一次 消费确认 | 支持 至少一次、最多一次 消费确认 |
| 消息投递 | 支持 延迟投递 和 定时投递 消息 | 支持 定时消息 和 延时消息 |
| 事务支持 | 支持分布式事务,精确一次消费模型(exactly-once) | 支持分布式事务 |
| 消息过滤 | 基于标签的简单过滤 | 支持 SQL 92 风格的消息过滤 |
| 协议 | 自定义协议,支持开源协议和其他协议 | 自定义协议,支持多种协议,包括开源协议 |
| 扩展性 | 支持动态扩展、分区扩展非常方便 | 支持动态扩展,能够更灵活地适应消息流量变化 |
| 社区活跃度 | 由 Apache 维护,社区活跃,生态成熟 | 由阿里巴巴维护,生态逐步丰富,社区活跃 |
| 与大数据生态的集成 | 与 Hadoop、Spark 等大数据组件紧密集成 | 与 Hadoop、Flink、Spark 等大数据组件集成较好 |
| 部署 | 易于集群部署、管理复杂度较高 | 易于部署,但与 Kafka 相比,管理和配置稍微复杂 |
| 使用场景 | 大数据流处理、日志收集、实时分析 | 实时消息推送、金融领域、高并发场景 |
| 可靠性 | 高可靠性,保证消息不丢失 | 高可靠性,消息投递确保不丢失,支持多个副本 |
| 国际化支持 | 已有广泛的国际化应用,特别是在北美和欧洲地区 | 主要在中国及亚洲市场应用较多,国际化支持逐步完善 |
| 消息延时 | 不支持(需要扩展实现) | 支持定时和延时消息 |
| 生态工具 | 丰富(如 Kafka Streams, Kafka Connect) | 生态工具少,需更多手动开发 |
| 消息路由 | 生产者决定分区,分区策略灵活 | 消息队列精细化管理,支持标签(Tag)过滤 |
| 性能 | 高吞吐、批量消息处理优异 | 高并发、低延迟场景更适合 |
| 可靠性 | 依赖副本和 Zookeeper 管理 | Master-Slave 架构,可靠性更高 |
| 生态 | 成熟,工具链丰富 | 工具链相对较少 |
同一个分区只能由消费者组中的一个消费者消费的原因
在 Kafka 中,同一个分区只能由消费者组中的一个消费者消费,这是 Kafka 的设计决策之一,目的是保证消息的顺序性和避免重复消费。这背后有几个关键的原因:
保证消息顺序性
Kafka 中的每个 分区(Partition) 是一个有序的消息队列,每个分区内的消息是严格有序的。为了保证消息的顺序性,每个分区内的消息必须由单个消费者按顺序消费。如果一个分区同时由多个消费者消费,那么这些消费者就可能并行处理分区中的消息,导致消息的顺序被打乱。
避免重复消费
如果同一个分区允许多个消费者同时消费,那么就可能会出现一个消费者处理了一条消息后未及时提交偏移量(offset),而另一个消费者也开始消费该消息的情况。这样就会导致 重复消费,进而引发数据的不一致性。
分区的负载均衡
每个消费者组有一个 消费者协调器(Consumer Coordinator),它负责管理消费者的分配和负载均衡。消费者组中的每个消费者都会负责消费一个或多个分区。但同一个分区只能被消费者组中的一个消费者负责,这样可以避免对同一个分区进行多次消费,确保数据性能优化。
消费者的偏移量管理
Kafka 使用偏移量(offset)来追踪每个消费者在分区中的消费进度。消费者会向 Kafka 提交它当前消费的最新偏移量。为了确保消息消费的进度正确且不重复,必须保证一个分区只能由一个消费者处理。
消费者重平衡(Rebalancing)
当消费者组中的消费者数量发生变化时,Kafka 会进行 重平衡(rebalance),重新分配分区给消费者。此时,每个分区只能有一个消费者进行消费,以确保消费的准确性和效率。如果允许多个消费者同时消费同一分区,重平衡过程中就会增加复杂度,并且会造成消费过程中的不一致和潜在的错误。
作为大数据处理首选框架的原因
高吞吐量和可扩展性
高吞吐量:Kafka 使用分布式架构,支持批量处理和零拷贝技术,减少了 I/O 操作,能够轻松处理每秒数百万条消息。
可扩展性:Kafka 的分区机制支持水平扩展。通过增加分区和消费者组,可以轻松扩展以适应大规模数据流需求。
相比之下
RabbitMQ:虽然可靠性和灵活性很好,但它更适合低延迟和复杂路由的消息场景,对于高吞吐量需求的支持不如 Kafka。
RocketMQ:性能较好,但 Kafka 在全球社区支持、生态系统和成熟度方面更胜一筹。
持久化存储和高可靠性
消息持久化:Kafka 将消息存储在磁盘中,并通过分区副本机制实现高可靠性。即使消费者没有及时消费消息,数据也可以长期保留。
顺序性保证:分区内的消息是有序的,便于日志分析和流式处理。
分区副本:Kafka 使用多副本机制,在 Broker 节点故障时,仍然能够确保数据的高可用性。
相比之下
RabbitMQ:支持消息持久化,但性能在高并发和大量消息持久化场景下表现不如 Kafka。
RocketMQ:也支持持久化,但生态和易用性不及 Kafka。
数据流处理生态系统
Kafka 与其生态工具(如 Kafka Streams 和 Kafka Connect)紧密集成,非常适合大数据场景。
Kafka Streams:内置的流处理 API,可以直接在 Kafka 上进行实时流式处理。
Kafka Connect:支持与各种数据源和目标(如 HDFS、Elasticsearch、数据库)进行无缝集成。
大数据生态:Kafka 是 Hadoop、Spark、Flink 等大数据框架的标准组件,许多框架对 Kafka 提供开箱即用的支持。
相比之下
RabbitMQ:缺乏类似的生态系统和大数据工具支持。
RocketMQ:虽然也支持流式处理,但相关工具和社区支持不如 Kafka 成熟。
高并发和消费者灵活性
Kafka 使用消费者组机制,支持高并发消费和灵活的消费者逻辑。
消费者组:多个消费者可以并行消费不同的分区,提升消费能力。
回溯消费:Kafka 允许消费者按需重新读取历史消息,这对于大数据处理中的错误重试和回溯分析非常重要。
相比之下
RabbitMQ:不支持回溯消费,消费者一旦确认消息,无法重新读取。
RocketMQ:支持重试和回溯,但实现和使用复杂度略高于 Kafka。
大规模日志采集和实时处理的天然适配
Kafka 起源于 LinkedIn 的大规模日志采集需求,设计上非常适合以下场景:
日志聚合:Kafka 能够高效地采集、存储和转发大规模分布式日志。
实时处理:结合流式处理工具(如 Flink 或 Spark Streaming),Kafka 是处理实时数据的核心组件。
相比之下
RabbitMQ:更擅长短生命周期的消息队列,无法高效处理海量日志数据。
RocketMQ:支持类似功能,但缺乏 Kafka 在业界的广泛应用和生态优势。
开源社区和生态系统
Kafka 是 Apache 基金会的顶级项目,全球范围内有庞大的社区支持:
社区活跃度:Kafka 拥有丰富的文档、案例和社区支持。
具链支持:许多开源和商业工具对 Kafka 提供集成支持。
跨平台部署:Kafka 易于在云平台和本地数据中心之间部署,且支持 Kubernetes 等现代容器化环境。
相比之下
RabbitMQ:虽然有较好的支持,但主要用于传统企业应用。
RocketMQ:虽然也有一定的社区支持,但规模和影响力不如 Kafka。
成本和运维复杂度
Kafka:依赖于 ZooKeeper 实现集群协调(未来版本使用 Kafka Raft 替代),但总体上在高吞吐量场景下的成本较低。
RabbitMQ:需要更多资源来处理高吞吐量场景的消息存储和消费。
RocketMQ:运维复杂度低于 Kafka,但资源利用效率不如 Kafka 高。
Kafka 是大数据领域的首选消息中间件,因为它在吞吐量、持久化、扩展性和生态支持上具备综合优势。如果系统对消息延迟、复杂路由要求较高,或者不需要处理海量数据时,RabbitMQ 或 RocketMQ 可能更合适。
选举机制
Kafka 的选举机制在 Zookeeper 模式 和 KRaft 模式 下有所不同,主要体现在 领导选举 和 集群元数据管理 的方式上。
Zookeeper 模式下的选举机制
在早期的 Kafka 架构中,集群的元数据管理和选举机制完全依赖 Zookeeper。Kafka 使用 Zookeeper 进行集群的协调,Zookeeper 负责分区副本的领导选举、元数据存储以及故障恢复。
Zookeeper 的作用
- 分区领导选举:每个 Kafka 分区会有一个领导副本(leader replica)负责处理读写请求,其他副本是跟随副本(follower replica)。如果一个分区的领导副本失败,Zookeeper 会触发 领导选举,选择一个新的副本作为该分区的领导副本。
- 集群元数据管理:Zookeeper 存储 Kafka 集群的元数据,例如每个分区的副本分配情况、消费者组的消费进度等。Zookeeper 会确保集群的元数据一致性。
- Broker 监控与故障检测:Zookeeper 会监控 Kafka broker 的健康状态。当某个 broker 宕机时,Zookeeper 会及时进行故障检测,并触发领导选举和副本恢复。
选举流程
- Zookeeper 维护节点状态:每个 Kafka broker 都会向 Zookeeper 注册,作为集群的一部分。当 broker 启动时,它会在 Zookeeper 中注册一个节点(例如
/brokers/ids/<broker_id>)。 - 领导选举:对于每个 Kafka 分区,Zookeeper 会在多个副本之间选择一个副本作为领导副本(leader)。Zookeeper 会使用 临时节点(ephemeral node)来标识领导副本。临时节点在 broker 失败时会自动删除,触发新的领导选举。
- 故障恢复:如果某个领导副本宕机,Zookeeper 会感知到该节点的失败,并进行新的领导选举,确保分区始终有一个活跃的领导副本。通过 Zookeeper 的协调,Kafka 可以确保高可用性。
- Leader 选举算法:Zookeeper 采用 Zab 协议(Zookeeper Atomic Broadcast),这个协议保证了集群中的 顺序一致性,确保同一时间内只有一个分区副本被选为领导副本。
优点:
- Zookeeper 的一致性协议和分布式协调机制确保了 Kafka 集群在分区领导选举方面的一致性。
- 分区领导选举和元数据管理的协调工作由 Zookeeper 完成,能够保证系统高可用和故障恢复。
缺点:
- 性能瓶颈:Zookeeper 在大规模 Kafka 集群中会成为性能瓶颈,特别是在集群规模扩展时,Zookeeper 的一致性协议(如同步更新)会影响性能。
- 复杂性:Kafka 对 Zookeeper 的依赖增加了系统的复杂性,要求用户必须维护 Zookeeper 集群。
KRaft 模式下的选举机制
在 KRaft 模式(Kafka Raft)下,Kafka 完全摆脱了 Zookeeper 的依赖,转而使用 Raft 协议进行集群元数据的管理和领导选举。KRaft 模式简化了 Kafka 集群的架构,提升了系统的性能和可靠性。
KRaft 模式的核心组件
- KRaft Controller:KRaft 模式中有一个新的组件称为 KRaft Controller,它负责集群的元数据管理、分区副本的领导选举以及集群状态的协调。多个 Kafka 节点可以充当 Controller,组成 Controller Quorum。
- Raft 协议:Kafka 在 KRaft 模式下采用 Raft 协议,这是一种一致性协议,确保在分布式系统中,各个节点之间的状态一致性。Raft 协议确保集群的高可用性,领导选举和副本同步能够在没有 Zookeeper 的情况下进行。
KRaft 选举流程
- Kafka Controller Quorum:在 KRaft 模式下,Kafka 集群中的 KRaft Controller 节点负责管理和协调集群的元数据。多个 broker 可以成为 Controller,组成一个 Controller Quorum。当集群启动时,Kafka 会选举出一个 Controller 节点,作为集群的管理者。
- Raft 协议实现领导选举:
- 每个 Kafka 分区有一个 领导副本(leader replica),用于处理所有读写请求,其他副本作为 跟随副本(follower replica)进行同步。
- Raft 协议用于管理领导副本的选举。集群中的 Controller 会使用 Raft 协议进行协调,确保每个分区都有一个领导副本。Controller 节点会通过 Raft 协议与集群中的其他节点进行沟通,确保数据一致性。
- 副本同步与故障恢复:
- 在 KRaft 模式下,Raft 协议还负责副本同步和故障恢复。当某个领导副本不可用时,Raft 会选举出一个新的领导副本。Raft 协议保证了副本的高可用性和数据一致性。
- 无 Zookeeper 的元数据管理:
- 在 KRaft 模式下,Kafka 完全去除 Zookeeper,所有的集群元数据(如主题、分区、副本和消费者偏移量等)都由 Kafka Controller 通过 Raft 协议进行管理。
- Raft 协议的日志复制机制保证了元数据的持久化和一致性。每次元数据变更时,都会通过 Raft 协议同步到集群中的所有节点。
优点:
- 去除 Zookeeper:Kafka 不再依赖 Zookeeper,简化了集群架构,减少了运维复杂度。
- 高性能:Raft 协议的实现避免了 Zookeeper 带来的性能瓶颈,提升了 Kafka 的吞吐量和可扩展性。
- 一致性保障:Raft 协议确保了 Kafka 集群在没有 Zookeeper 的情况下依然能保证元数据的一致性和高可用性。
缺点:
- 过渡期不稳定:尽管 KRaft 模式在 Kafka 2.8.0 中开始支持,但在一些大规模部署中,可能会面临过渡期的稳定性问题。需要时间和社区的不断迭代和优化。
Zab 协议和 Raft 协议
Zab 协议 和 Raft 协议 都是分布式系统中的一致性协议,它们的目标是确保在多节点的环境下,数据的一致性和高可用性。虽然它们的目的相似,但它们的设计理念和实现机制有所不同。
Zab 协议 (Zookeeper Atomic Broadcast)
Zab 协议 是 Zookeeper 中用于保证数据一致性的协议,专门用于分布式协调系统中,确保消息广播的一致性。其具有以下主要特点
- 原子广播:Zab 协议保证消息在集群中的广播是原子的,即要么所有节点接收到这个消息,要么没有节点接收到。
- 领导选举:Zab 协议通过选举一个领导节点(leader)来协调集群中的所有数据变更请求。领导节点负责处理所有的写操作和数据同步,而其他节点作为跟随节点(follower),同步数据并保持一致。
- 恢复性:Zab 协议设计了一种容错机制,当出现节点故障时,Zookeeper 集群能够自动恢复到一致的状态。具体来说,如果领导节点宕机,Zab 协议会触发一个新的领导选举,确保系统的高可用性。
- 批量提交:Zab 协议使用批量的日志提交机制,将多个请求合并成一个批次进行提交,从而提高性能。
工作过程
- 领导节点选举:当 Zookeeper 启动时,会通过选举机制选举出一个领导节点。领导节点负责处理所有的写操作请求,确保数据的顺序一致性。
- 消息广播:所有的写操作请求都会通过领导节点广播到所有的跟随节点。领导节点将请求以事务日志的形式发送给所有的跟随节点。
- 确认与提交:所有的跟随节点会将接收到的消息存储在本地的事务日志中,并向领导节点确认。如果大多数节点(超过半数)确认收到消息,领导节点就会将该消息提交到集群中的所有节点。
- 故障恢复:如果领导节点宕机,Zookeeper 会自动进行新的领导选举,确保集群能够继续提供服务。
优点:
- 确保了数据一致性和顺序性。
- 可以保证在多数节点不可用的情况下集群仍然能够继续工作(容错性强)。
缺点:
- 相较于 Raft 协议,Zab 协议的实现相对复杂。
- 对于写操作的处理,Zab 协议的延迟较高,因为它依赖于领导节点的同步过程。
Raft 协议
Raft 协议 是一种旨在简化分布式一致性协议的算法。Raft 协议的核心目标是让分布式系统中的所有节点保持数据一致性,并且尽可能让协议容易理解。Raft 协议主要用于保证集群中的日志一致性,是一种状态机复制协议。其具有以下主要特点:
- 领导选举:Raft 协议的工作原理依赖于一个集群中始终有一个领导者(leader)。领导者负责处理所有的客户端请求,并将日志条目复制到集群中的其他节点(follower)。
- 日志复制:当客户端向集群发送请求时,领导者会将请求记录为日志条目,并将这些条目复制到所有的跟随节点。确保集群中的每个节点都有相同的日志条目,从而保证一致性。
- 一致性保证:Raft 协议通过保证日志的复制顺序,确保了分布式系统中的数据一致性。只有当大多数节点都确认接收到日志条目时,领导者才会提交该条目,保证系统的强一致性。
- 故障恢复:Raft 协议可以容忍部分节点的故障,并通过领导选举机制确保集群的高可用性。如果领导节点宕机,Raft 会启动一个新的选举过程,确保新的领导节点可以继续管理集群。
工作过程
- 领导选举:Raft 集群中的节点分为三类:领导者(leader)、跟随者(follower)和候选者(candidate)。当集群启动时,集群中会选举出一个领导节点。领导节点负责处理所有的客户端请求。
- 日志复制:当领导节点收到客户端请求时,它将请求存储为日志条目,并将日志条目复制到所有的跟随节点。当大多数节点确认接收到日志条目时,领导节点才会将该条目提交并响应客户端。
- 心跳机制:领导节点会定期向跟随节点发送心跳信号(AppendEntries),以防止它们成为候选节点。
- 领导选举(故障恢复):当领导节点宕机时,Raft 协议会通过选举过程选举出一个新的领导节点。集群中的其他节点将重新同步最新的日志条目,确保系统一致性。
优点:
- Raft 协议比 Zab 协议更容易理解,设计简单明了。
- 它具有良好的容错性,可以在部分节点宕机的情况下继续运行。
- 可以实现强一致性和高可用性。
缺点:
- 相较于 Zab 协议,Raft 协议在实现上对集群中节点的数量有一定的限制,尤其是在集群规模很大的情况下。
批次
Kafka 中的批次指的是生产者一次性发送到 Kafka 集群的一组消息。生产者会把消息放入一个批次中,批次到达指定大小或时间后,一并发送到 Kafka 的 Broker。
批次不仅可以减少网络开销,还能提高吞吐量,因为 Kafka 对批次数据进行批量写入,而不是每条消息都单独写入。
批次的两大核心参数:
- 批次大小(Batch Size):指定批次中消息的最大字节数,Kafka 会将不超过这个大小的消息放入同一个批次。
- 配置项:
batch.size - 默认值:16 KB
- 配置项:
- 批次时间(Linger Time):指定生产者等待新的消息到来并加入到当前批次的最大时间。如果达到批次大小之前,这个时间已过,生产者会立即发送批次中的消息。
- 配置项:
linger.ms - 默认值:0(即不等待,立即发送)
- 配置项:
这两个参数决定了消息的发送批次大小和延迟,合理的配置可以在吞吐量和延迟之间找到平衡。
Kafka 生产者会将消息积累在一个内存缓冲区中,一旦达到以下条件中的任何一个,消息就会被批量发送到 Kafka Broker:
- 批次大小达到配置的
batch.size。 - 批次等待时间超过配置的
linger.ms。
一旦一个批次准备好,生产者会将这个批次的所有消息一次性发送到 Kafka 的某个分区。Kafka Broker 会将消息按顺序存储在对应的分区中。
🛠 优化建议:
- 适当增大
batch.size:增大批次大小可以提升吞吐量,但也可能带来更大的延迟,因此需要根据应用场景进行调整。 - 调整
linger.ms以平衡延迟和吞吐量:如果消息生产的速度较慢,增加linger.ms可以增加批次的大小,从而提高吞吐量;如果对延迟敏感,可以将其设为 0 或者设置较小的值。 - 使用压缩:开启消息压缩(如
gzip或snappy)可以进一步节省带宽和存储空间,特别是在高流量场景下。 - 控制
buffer.memory:调整buffer.memory的大小来控制内存使用,当生产者发送大量消息时,合适的内存配置能够提高性能,避免缓冲区溢出。
生产者分区策略
Kafka 生产者将消息选择性地分配给某个分区,选择方式一般有以下几种:
✅ 1. 默认轮询策略(Round Robin)
如果生产者没有提供消息的 Key,或者指定了默认的分区器(DefaultPartitioner),那么 Kafka 生产者会采用 轮询(Round Robin) 策略:
- 消息会均匀地分配到各个分区,避免某些分区的消息过多,而其他分区则过少。
- 这个策略不保证消息的顺序性。
🎯 适用场景:当你不关心消息的顺序性,且希望消息尽可能均匀地分布到各个分区时,可以使用此策略。
✅ 2. 基于 Key 的哈希分区策略
最常见的 Kafka 生产者分区策略是基于消息的 Key(如订单 ID、用户 ID 等)来决定消息应该发送到哪个分区。
- Kafka 使用生产者传入的 消息 Key 通过哈希算法(通常是 Murmur2 哈希)计算出一个分区号。
- 这个分区号会确定消息的目标分区,使得具有相同 Key 的消息会被发送到同一个分区。
🎯 适用场景:当你希望具有相同 Key 的消息(例如同一个用户的消息、同一个订单的消息)始终发送到同一个分区,并且保持顺序时。
✅ 3. 自定义分区策略(Custom Partitioner)
如果 Kafka 默认的分区策略不能满足你的需求,Kafka 允许你自定义一个分区器(Partitioner)。你可以通过实现 org.apache.kafka.clients.producer.Partitioner 接口来实现自己的分区逻辑。
自定义分区器可以让你基于任何自定义的逻辑来选择分区。例如,可以根据消息内容、时间戳、特定字段等来决定消息应该发送到哪个分区。
消费者组
消费者组是一组协同工作的消费者(Consumer 实例),它们共同消费一个或多个 Topic 的数据,并且每条消息只被组内一个消费者处理一次。
🔄 使用消费者组的原因
| 能力 | 说明 |
|---|---|
| ✅ 负载均衡 | 多个消费者可以并行消费同一个 Topic,不抢活干 |
| ✅ 高吞吐 | 结合 Topic 分区机制,实现并发消费 |
| ✅ 容错性高 | 某个消费者宕机,Kafka 自动将它的分区分配给组内其他消费者(重平衡) |
| ✅ 消息语义明确 | 每条消息只会被一个消费者消费一次(组内) |
消息堆积问题
Kafka 产生消息堆积的本质原因是:
⚠️ “消费速度 < 生产速度”,也就是:写入太快,处理太慢。
1️⃣ 消费者处理能力不足
- Kafka Topic 的消息不断增加
- Lag 指数级增长
- Broker 没有问题,消费者慢
🔹可能原因:
- 单条消息处理耗时太长(慢 SQL、复杂计算、IO 阻塞)
- 消费端没有批量处理(如每条都单独写库)
- 线程池饱和,或只用单线程处理消息
✅ 解决方案:
- 批量消费 & 批量写入
- 使用线程池多线程处理 Partition 内消息
- 优化耗时逻辑(如缓存、异步)
2️⃣ 消费者实例数过少
- 消费速率很低,多个分区挂在一个消费者上
- Lag 无法下降
- Kafka 是按 Partition 并发消费,一个分区只能由一个消费者消费
- 所以消费者数 < 分区数 时,会有消费者“分身乏术”
✅ 解决方案:
- 增加消费者实例数(保持 ≤ 分区数)
- 或增加 Topic 的分区数量
3️⃣ 分区数量不足,无法并发消费
- 消费者数足够,但部分线程闲着没活干
- 一个分区只能被一个 ConsumerGroup 的一个实例消费
- 分区太少 → 并发上限太低 → 消费吞吐不足
✅ 解决方案:
- 增加分区数(如从 3 提升到 10)
- 注意:分区数变更会影响“消息顺序性”,需评估
4️⃣ 消费端异常,消费失败或崩溃
- 消费端无日志 / 报错
- 消费程序挂掉,或无法处理消息
- 应用宕机或 GC 卡顿
- 消息格式异常导致代码抛错
- Kafka 重平衡频繁,消费者反复被踢出 Group
✅ 解决方案:
- 增加消费端容错:try-catch、DLQ(死信队列)
- 设置自动重启机制
- 检查监控、报警系统
5️⃣ offset 没有及时提交
- 实际消费成功了,但 Kafka 认为“还没消费”
enable.auto.commit=false,你使用手动提交 offset,但忘了提交- 或提交逻辑出错(比如只在程序退出前才提交)
✅ 解决方案:
- 正确配置 offset 提交机制
- 要么启用自动提交(适用于幂等消费)
- 要么在业务逻辑处理完成后显式提交 offset
6️⃣ 消费逻辑阻塞或卡死
- 消费线程卡住,没处理新消息,也不报错
- 死锁、线程阻塞、数据库连接池耗尽
- 网络阻塞或服务依赖挂掉
✅ 解决方案:
- 加超时机制 + 降级策略
- 用线程池 + 监控线程运行状态
7️⃣ Broker 写入过慢,副本同步慢
- Producer 发送变慢,Broker CPU/disk 飙高
- consumer 其实空闲,仍然 Lag 增长
- Broker 网络、磁盘 IO 压力大
- 副本同步慢,ISR 频繁变化,leader 切换
- Topic 副本配置不合理(如副本数太多)
✅ 解决方案:
- 优化磁盘(SSD)、提升网络带宽
- 调整副本数、改写
replica.fetch.max.bytes等参数
如果短时间堆积严重
- 临时快速扩容消费者数
- **将堆积消息写入临时队列(如 Redis)**做缓冲,再慢慢消费
调优
🎯 调优主要目标:
- 提高吞吐量
- 降低延迟
- 保证可靠性
- 提高稳定性
💡 Producer(生产者)调优
| 参数 | 描述 | 建议 |
|---|---|---|
batch.size |
每个 batch 的最大消息体积(单位:字节) | 加大,如 32KB 或 64KB,可提升吞吐 |
linger.ms |
批量等待时间(消息填不满就等这时间) | 如 5~20ms,适当延迟可提高批量效果 |
compression.type |
压缩类型(gzip/snappy/lz4/zstd) | 推荐 lz4 或 zstd(快且压缩好) |
acks |
0 / 1 / all,控制可靠性 | 推荐用 acks=all,更安全 |
retries |
失败重试次数 | 建议设置为 3~10,防临时网络抖动 |
enable.idempotence |
开启幂等性,防止重复写 | 一般都开启,避免重复消息 |
💡Broker(服务端)调优
| 参数 | 描述 | 建议 |
|---|---|---|
num.network.threads |
处理网络请求的线程数 | 足够高,如 3~8,取决于 CPU |
num.io.threads |
处理磁盘 IO 的线程数 | 一般与网络线程数量相当 |
log.retention.hours / log.retention.bytes |
控制日志多久删除 | 看业务需求,别让磁盘爆了 |
log.segment.bytes |
单个日志文件大小 | 默认 1GB,可调成 512MB |
log.flush.interval.messages/ms |
刷盘策略 | 默认异步,别改动 unless 非要强一致 |
replica.fetch.max.bytes |
follower 拉取数据最大值 | 设置大点,比如 10MB+ 提高复制效率 |
message.max.bytes |
单条消息最大值 | 默认 1MB,根据业务调整 |
💡Consumer(消费者)调优
| 参数 | 描述 | 建议 |
|---|---|---|
fetch.min.bytes |
每次拉取的最小数据量 | 设置大点可以减少请求次数 |
fetch.max.bytes |
每次最大拉取数据量 | 设置为 10MB 级别 |
max.poll.records |
每次 poll 的最大消息数 | 根据处理能力设,避免堆积 |
enable.auto.commit |
自动提交 offset? | 实时场景慎用,建议改为手动提交 |
max.poll.interval.ms |
每次处理最大耗时 | 设太小容易触发 rebalancing |
系统层面调优(重点)
✅ 磁盘(最关键)
- 使用 SSD(顺序写也能爆炸快)
- 文件系统推荐 XFS 或 EXT4
- 分区对齐、IO调度器调整为 noop
✅ 操作系统参数
# 文件句柄数
ulimit -n 1000000
# 网络缓冲区
sysctl -w net.core.rmem_max=2500000
sysctl -w net.core.wmem_max=2500000
# TCP 优化
sysctl -w net.ipv4.tcp_window_scaling=1
ZooKeeper / KRaft 调优(如用)
- 降低 GC:使用
G1GC或ZGC tickTime、initLimit、syncLimit参数根据节点数量优化- Kafka 3.3+ 推荐使用 KRaft 模式,逐步摆脱 ZooKeeper
这三个参数是 Kafka(在使用 ZooKeeper 模式下)配置中的关键参数,尤其是在 zoo.cfg 里配置 ZooKeeper 集群的时候,起着非常重要的作用。
它们控制的是 ZooKeeper 集群中 leader 和 follower 之间的心跳、连接超时、同步时间窗口等行为。
🔧 运行时调优建议
- 观察指标:Kafka 自带 JMX 指标,可对接 Prometheus + Grafana
- 定期清理无用 topic 和 consumer group
- 分区数量要“恰当”:
- 过少 → 吞吐不足
- 过多 → 管理成本高、元数据膨胀
高吞吐量的原因
✅ 1. 顺序写磁盘(磁盘也能飞)
- Kafka 的消息写入是追加到日志末尾,顺序写磁盘,比随机写快很多。
- 配合操作系统的 Page Cache,写磁盘前会先进入内存缓冲区,加快写入速度。
💡 顺序写 + Page Cache ≈ 贴着内存的速度在跑
✅ 2. 零拷贝(Zero-Copy)技术
- Kafka 使用 Linux 的
sendfile系统调用,避免用户态 ↔ 内核态之间的多次拷贝。 - 减少了 CPU 和内存的消耗,加快了数据从磁盘到网络的传输速度。
💡 普通流程是:磁盘 → 内核 → 用户空间 → 内核 → 网络
Kafka 是:磁盘 → 内核 → 网络,直接一步到位!
✅ 3. 批量处理(Batching)
- 生产者端、Broker端、消费者端都支持 批量发送和拉取消息。
- 比如生产者可以把多条消息合成一个 batch 发送,消费者也可以一次拉一批。
- 减少了网络请求数量,极大提升吞吐。
✅ 4. 分区并行机制(Partition)
- 一个 Topic 可以有多个 Partition,每个 Partition 都可以并发读写。
- 多线程并发处理不同 Partition,吞吐自然就上去了。
- 可以横向扩展到更多机器(Broker)。
💡 Partition = Kafka 并行度的单位
✅ 5. 高效的存储结构(Log + Index)
- Kafka 的日志文件按分段(segment)存储,每个 segment 文件都有索引。
- 查找消息时先找索引,再定位 offset,定位速度快,查找代价低。
✅ 6. 异步写入 + 异步复制
- Kafka 允许异步将消息写入磁盘、异步副本同步(可调策略,视可靠性要求)。
- 对性能要求高的场景,可以牺牲一点一致性换来极高吞吐(例如设置
acks=1)。
✅ 7. 客户端轻量 + Broker无状态(偏流式)
- Kafka 的 Broker 只负责写入、存储、分发消息,逻辑简单,不需要记住谁消费了什么(消费位移由客户端提交)。
- 这让 Kafka Broker 非常“轻快”,更容易扩容,吞吐不被拖累。
✅ 8. 压缩机制(压缩消息体)
- 支持 GZIP、Snappy、LZ4、ZSTD 等压缩算法,减小消息体积。
- 减少网络 IO 和磁盘 IO,提升整体处理能力。
过多的 topic 导致整体上性能变慢的原因
✅ 1. 每个 topic 分区会增加 broker 的负担
Kafka 中一个 topic 通常有多个 partition,而每个分区都会占用:
- 文件句柄(open file descriptor);
- 内存 page cache;
- 对应线程的调度负担(日志写入、flush、replica 复制等);
- 操作系统资源(如 mmap 映射、I/O 缓冲);
topic 多 → 分区多 → 系统资源消耗上升 → 性能下降。
✅ 2. Controller 元数据管理压力变大
Kafka 的 Controller 节点会管理所有 topic/partition 的元信息。
当 topic 数量很多时,Controller 需要:
- 跟踪每个分区的 leader 和 ISR;
- 频繁处理元数据变更事件;
- 每次新 broker 加入或故障恢复时,都会有大规模重平衡;
- 元数据同步的延迟也会上升,集群稳定性下降。
✅ 3. 生产者 & 消费者客户端压力上升
- 生产者/消费者在启动时会加载所有 topic 的元信息;
- 如果 topic 很多,客户端初始化、元数据刷新会变慢;
- 消费者负载分配(rebalance)耗时大大增加;
- 监控和管理工具(如 Kafka UI、Prometheus)也变得更慢。
✅ 4. 垃圾回收 (GC) 压力加大
分区多 → segment 文件多 → 活跃线程多 → 内存分配频繁 → GC 压力增加。
这会导致:
- 更频繁的 stop-the-world;
- 更高的延迟和不可预测的性能波动。
✅ 5. Broker 启动变慢
Kafka Broker 启动时会 scan 所有日志目录下的分区数据,进行恢复、索引重建等:
- topic 越多,分区越多,启动时间越长;
- 恢复过程越慢,容易出现节点长时间“不可用”。
✅ Kafka 官方建议:
| 项目 | 建议上限 |
|---|---|
| 每个 broker 的 topic 数量 | 通常建议 < 10,000 个 |
| 每个 broker 的分区数 | 建议 < 4,000 ~ 10,000 个 |
| 每个 topic 分区数 | 控制在 50~100 以内 |
当然,这些上限依赖硬件配置、Kafka 版本和业务特性。
✅ 实际建议
- 避免每个用户/业务都新建 topic;
- 使用 共享 topic + 标签(如 header 或 key)进行逻辑区分;
- topic 归档、合并不活跃的 topic;
- 启用 Kafka 2.4+ 的分区级别元数据管理优化;
- 定期清理长时间不用的 topic。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)