简介

Kafka 是一个分布式流处理平台,可以用来:

🚚 高效地收集、传输、存储、处理 实时数据流。

它最初由 LinkedIn 开发,用于解决海量日志处理的问题,后来开源给 Apache,成为现在广泛应用的数据中枢系统。

Kafka 能做的事

能力 描述
消息队列 实现服务解耦,异步通信,类似 RabbitMQ、RocketMQ,但吞吐更高
日志采集系统 高性能写入、持久化、传输(用于 ELK、日志分析等)
事件驱动架构 系统之间通过“事件”交互,解耦微服务
实时数据流处理 配合 Flink、Spark 等组件做实时分析
数据管道(ETL) 把数据从业务系统、数据库、日志收集器汇总到数据仓库、Hadoop、ES 等

主要应用场景

  1. 系统解耦 + 异步通信
    • 订单系统 → 支付系统 → 发货系统,各系统通过 Kafka 消息传递,不强耦合,失败可容、削峰填谷。
  2. 大数据日志收集
    • 日志 → Kafka → Flink/Spark → HDFS/ES/DB。
    • ELK / Graylog / Hadoop 的数据入口常用 Kafka。
  3. 实时数据分析
    • 网站点击流、用户行为分析、金融交易监控、异常检测等。
    • Kafka + Flink/Spark 可以实现 毫秒级处理和响应
  4. 监控与告警系统
    • 各类监控数据汇总到 Kafka,再下发到告警中心、控制面板。
  5. 数据库变更同步(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 处理不过来,有的却很空闲;
  • 严重时会造成消费延迟、系统资源浪费甚至服务不稳定。

数据倾斜的原因:

  1. 生产者分区策略问题
    • 默认使用 key hash 分区器,如果 key 分布不均,hash 后也会不均;
    • 或者显式指定了某些 Partition,使得只有少数 Partition 被使用。
  2. 没有设置 key
    • Kafka 会使用轮询(Round-Robin)策略分发;
    • 如果某些 Producer 实现有问题或批次不均,也可能造成不均衡。
  3. 动态 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 不会自动解决,你需要:

  1. 优化生产者分区策略,确保 key 均匀分布或用 Round-Robin;
  2. 监控各 Partition 的消息量与延迟,定位热 Partition;
  3. 扩展 Topic 的 Partition,并重新设计 key 分布策略;
  4. 在消费端使用多线程消费单个 Partition(针对极端倾斜);
  5. 引入中间层流量调度逻辑(如 Kafka Connect、流处理框架 Flink/Spark Streaming 进行动态 repartition)。
  6. 自定义分区器 编写 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.messageslog.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=allretries > 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 判断哪些副本落后,需要进行数据同步。

✅ 高水位的作用

  1. 消费者消费的依据
    • 高水位标记了 消费者可以读取的最大偏移量,消费者只能消费 高水位之前 的消息。
    • 消费者在消费时,不会消费高水位之后的消息,确保消费数据的一致性和完整性。
    • 高水位也决定了 消费的进度,当消费者消费到某个偏移量时,Kafka 会更新这个消费者所在分区的高水位。
  2. 副本同步的保障
    • 高水位也作为所有副本的同步进度的标记,表示到高水位的消息,所有副本都已经同步并且保证数据一致性。
    • 只有当 Leader 的消息已经成功同步到所有 ISR(In-Sync Replicas)中的副本时,才会更新高水位。
  3. 数据的持久性
    • Kafka 中的高水位表示所有 ISR 中的副本都已经成功复制的数据位置。只有高水位以下的数据被认为是“已提交”的消息,才是可靠的。如果某个副本失效,只要高水位以下的数据已经被其他副本同步,这些数据就不会丢失,因此高水位是 Kafka 保证数据持久性的关键机制。
    • 这也确保了 Kafka 对消息的持久性承诺:即使某个副本失败,只要其他副本中有数据,它就不会丢失。

✅ 低水位的作用

  1. 副本的同步进度
    • 低水位反映了分区中副本同步进度的 最小值,如果某个副本落后于其他副本,它的同步进度就会影响到低水位的更新。
    • 当某个副本的日志跟不上其他副本的更新时,低水位就会保持在这个副本的最后同步偏移量。低水位有助于监控 副本的同步延迟
  2. 确保一致性
    • Kafka 使用低水位来确保数据的一致性。如果一个副本落后于 Leader 太多,它会被从 ISR(In-Sync Replicas)中移除,这意味着该副本不再与 Leader 保持同步,不再能保证数据的一致性。
    • 低水位帮助 Kafka 确保只有 同步的副本 才能成为 Leader,当一个副本被移出 ISR 时,Kafka 会选择其他副本作为 Leader,以避免数据丢失和不一致。
  3. 判断副本的健康状态
    • 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) 是一种优化技术,主要用于减少数据在用户空间和内核空间之间的拷贝次数,从而提升性能。它广泛应用于高性能网络编程、大文件传输和数据库系统中。

传统数据传输(例如文件发送到网络)的流程通常如下:

  1. 文件读取: 从磁盘读取文件到内核缓冲区。
  2. 数据拷贝: 将内核缓冲区的数据拷贝到用户空间缓冲区。
  3. 数据处理: 用户进程可能对数据进行处理。
  4. 数据发送: 将用户空间缓冲区的数据再次拷贝到内核缓冲区,通过网卡发送出去。

上述流程中,数据需要在 用户空间 和 内核空间 之间多次拷贝,造成以下问题:

  • CPU 消耗大: 每次拷贝都需要 CPU 参与。
  • 内存带宽浪费: 数据被重复拷贝,占用大量内存带宽。

零拷贝 是指在数据传输过程中,通过避免数据在用户空间和内核空间之间的冗余拷贝,从而减少 CPU 和内存带宽的开销。
它并不是完全没有拷贝,而是通过减少拷贝次数达到“接近零拷贝”的效果。

实现零拷贝的技术手段

  1. sendfile 系统调用
    • sendfile 是 Linux 提供的系统调用,常用于将文件数据直接从磁盘传输到网络,而无需经过用户空间。
    • 文件数据从磁盘读取到内核缓冲区。
    • 内核将数据直接从内核缓冲区复制到网络协议栈的缓冲区。
    • 网络协议栈将数据通过网卡发送。
  • 这样数据在整个过程中不需要进入用户空间。
    • 避免了用户空间和内核空间之间的拷贝。
    • 减少了上下文切换。
  1. mmap + write
    • mmap 是将文件映射到用户进程的虚拟地址空间,结合 write 可以减少一次数据拷贝。
    • 文件通过 mmap 映射到用户空间,与内核缓冲区共享内存。
    • 使用 write 将内核缓冲区的数据直接发送到网络。
    • 这种方法减少了一次从内核到用户空间的拷贝。
  2. Direct I/O
    • 通过绕过内核的页缓存,直接将数据从磁盘读取到网络协议栈的缓冲区,避免了内核缓冲区和用户空间缓冲区的拷贝。
    • 适用场景:高性能文件传输,例如 Nginx 和 Kafka 中广泛使用。
  3. DMA(Direct Memory Access)
    • 现代硬件支持 DMA,允许直接在硬件和内存之间进行数据传输,避免 CPU 参与拷贝操作。
    • 在网络传输中,网卡使用 DMA 将数据从内存读取并发送到网络。

优点

  1. 性能提升:
    • 减少了 CPU 消耗和内存带宽占用。
    • 提高了数据传输的吞吐量。
  2. 减少上下文切换:
    • 用户进程和内核进程之间的切换显著减少。
  3. 降低延迟:
    • 数据处理路径更短。

零拷贝的限制

  1. 硬件依赖:
    • 零拷贝技术需要操作系统和硬件支持(如 DMA、sendfile)。
  2. 适用场景有限:
    • 对于需要频繁修改或处理数据的场景,零拷贝的优势较小。
  3. 内存管理复杂:
    • 零拷贝可能带来复杂的内存管理问题,如缓冲区回收。

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 会及时进行故障检测,并触发领导选举和副本恢复。

选举流程

  1. Zookeeper 维护节点状态:每个 Kafka broker 都会向 Zookeeper 注册,作为集群的一部分。当 broker 启动时,它会在 Zookeeper 中注册一个节点(例如 /brokers/ids/<broker_id>)。
  2. 领导选举:对于每个 Kafka 分区,Zookeeper 会在多个副本之间选择一个副本作为领导副本(leader)。Zookeeper 会使用 临时节点(ephemeral node)来标识领导副本。临时节点在 broker 失败时会自动删除,触发新的领导选举。
  3. 故障恢复:如果某个领导副本宕机,Zookeeper 会感知到该节点的失败,并进行新的领导选举,确保分区始终有一个活跃的领导副本。通过 Zookeeper 的协调,Kafka 可以确保高可用性。
  4. 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 选举流程

  1. Kafka Controller Quorum:在 KRaft 模式下,Kafka 集群中的 KRaft Controller 节点负责管理和协调集群的元数据。多个 broker 可以成为 Controller,组成一个 Controller Quorum。当集群启动时,Kafka 会选举出一个 Controller 节点,作为集群的管理者。
  2. Raft 协议实现领导选举
    • 每个 Kafka 分区有一个 领导副本(leader replica),用于处理所有读写请求,其他副本作为 跟随副本(follower replica)进行同步。
    • Raft 协议用于管理领导副本的选举。集群中的 Controller 会使用 Raft 协议进行协调,确保每个分区都有一个领导副本。Controller 节点会通过 Raft 协议与集群中的其他节点进行沟通,确保数据一致性。
  3. 副本同步与故障恢复
    • 在 KRaft 模式下,Raft 协议还负责副本同步和故障恢复。当某个领导副本不可用时,Raft 会选举出一个新的领导副本。Raft 协议保证了副本的高可用性和数据一致性。
  4. 无 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 协议使用批量的日志提交机制,将多个请求合并成一个批次进行提交,从而提高性能。

工作过程

  1. 领导节点选举:当 Zookeeper 启动时,会通过选举机制选举出一个领导节点。领导节点负责处理所有的写操作请求,确保数据的顺序一致性。
  2. 消息广播:所有的写操作请求都会通过领导节点广播到所有的跟随节点。领导节点将请求以事务日志的形式发送给所有的跟随节点。
  3. 确认与提交:所有的跟随节点会将接收到的消息存储在本地的事务日志中,并向领导节点确认。如果大多数节点(超过半数)确认收到消息,领导节点就会将该消息提交到集群中的所有节点。
  4. 故障恢复:如果领导节点宕机,Zookeeper 会自动进行新的领导选举,确保集群能够继续提供服务。

优点

  • 确保了数据一致性和顺序性。
  • 可以保证在多数节点不可用的情况下集群仍然能够继续工作(容错性强)。

缺点

  • 相较于 Raft 协议,Zab 协议的实现相对复杂。
  • 对于写操作的处理,Zab 协议的延迟较高,因为它依赖于领导节点的同步过程。

Raft 协议
Raft 协议 是一种旨在简化分布式一致性协议的算法。Raft 协议的核心目标是让分布式系统中的所有节点保持数据一致性,并且尽可能让协议容易理解。Raft 协议主要用于保证集群中的日志一致性,是一种状态机复制协议。其具有以下主要特点

  • 领导选举:Raft 协议的工作原理依赖于一个集群中始终有一个领导者(leader)。领导者负责处理所有的客户端请求,并将日志条目复制到集群中的其他节点(follower)。
  • 日志复制:当客户端向集群发送请求时,领导者会将请求记录为日志条目,并将这些条目复制到所有的跟随节点。确保集群中的每个节点都有相同的日志条目,从而保证一致性。
  • 一致性保证:Raft 协议通过保证日志的复制顺序,确保了分布式系统中的数据一致性。只有当大多数节点都确认接收到日志条目时,领导者才会提交该条目,保证系统的强一致性。
  • 故障恢复:Raft 协议可以容忍部分节点的故障,并通过领导选举机制确保集群的高可用性。如果领导节点宕机,Raft 会启动一个新的选举过程,确保新的领导节点可以继续管理集群。

工作过程

  1. 领导选举:Raft 集群中的节点分为三类:领导者(leader)、跟随者(follower)和候选者(candidate)。当集群启动时,集群中会选举出一个领导节点。领导节点负责处理所有的客户端请求。
  2. 日志复制:当领导节点收到客户端请求时,它将请求存储为日志条目,并将日志条目复制到所有的跟随节点。当大多数节点确认接收到日志条目时,领导节点才会将该条目提交并响应客户端。
  3. 心跳机制:领导节点会定期向跟随节点发送心跳信号(AppendEntries),以防止它们成为候选节点。
  4. 领导选举(故障恢复):当领导节点宕机时,Raft 协议会通过选举过程选举出一个新的领导节点。集群中的其他节点将重新同步最新的日志条目,确保系统一致性。

优点

  • Raft 协议比 Zab 协议更容易理解,设计简单明了。
  • 它具有良好的容错性,可以在部分节点宕机的情况下继续运行。
  • 可以实现强一致性和高可用性。

缺点

  • 相较于 Zab 协议,Raft 协议在实现上对集群中节点的数量有一定的限制,尤其是在集群规模很大的情况下。

批次

Kafka 中的批次指的是生产者一次性发送到 Kafka 集群的一组消息。生产者会把消息放入一个批次中,批次到达指定大小或时间后,一并发送到 Kafka 的 Broker。

批次不仅可以减少网络开销,还能提高吞吐量,因为 Kafka 对批次数据进行批量写入,而不是每条消息都单独写入。

批次的两大核心参数:

  1. 批次大小(Batch Size):指定批次中消息的最大字节数,Kafka 会将不超过这个大小的消息放入同一个批次。
    • 配置项batch.size
    • 默认值:16 KB
  2. 批次时间(Linger Time):指定生产者等待新的消息到来并加入到当前批次的最大时间。如果达到批次大小之前,这个时间已过,生产者会立即发送批次中的消息。
    • 配置项linger.ms
    • 默认值:0(即不等待,立即发送)

这两个参数决定了消息的发送批次大小和延迟,合理的配置可以在吞吐量和延迟之间找到平衡。

Kafka 生产者会将消息积累在一个内存缓冲区中,一旦达到以下条件中的任何一个,消息就会被批量发送到 Kafka Broker:

  1. 批次大小达到配置的 batch.size
  2. 批次等待时间超过配置的 linger.ms

一旦一个批次准备好,生产者会将这个批次的所有消息一次性发送到 Kafka 的某个分区。Kafka Broker 会将消息按顺序存储在对应的分区中。

🛠 优化建议:

  1. 适当增大 batch.size:增大批次大小可以提升吞吐量,但也可能带来更大的延迟,因此需要根据应用场景进行调整。
  2. 调整 linger.ms 以平衡延迟和吞吐量:如果消息生产的速度较慢,增加 linger.ms 可以增加批次的大小,从而提高吞吐量;如果对延迟敏感,可以将其设为 0 或者设置较小的值。
  3. 使用压缩:开启消息压缩(如 gzipsnappy)可以进一步节省带宽和存储空间,特别是在高流量场景下。
  4. 控制 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 的最大消息体积(单位:字节) 加大,如 32KB64KB,可提升吞吐
linger.ms 批量等待时间(消息填不满就等这时间) 5~20ms,适当延迟可提高批量效果
compression.type 压缩类型(gzip/snappy/lz4/zstd) 推荐 lz4zstd(快且压缩好)
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(顺序写也能爆炸快)
  • 文件系统推荐 XFSEXT4
  • 分区对齐、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:使用 G1GCZGC
  • tickTimeinitLimitsyncLimit 参数根据节点数量优化
  • 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。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐