1.架构概述
Kafka的架构通过Topic/Partition/Replica实现了分布式、并行处理和高可用。通过Consumer Group实现了可扩展的并行消费。其存储机制的核心是分区+分段+顺序追加写入+稀疏索引,结合对页缓存的巧妙利用和零拷贝技术,最大限度地发挥了磁盘顺序I/O的性能优势,同时保证了高效的消息查找。ISR机制和acks配置共同协作,在可用性和数据一致性/持久性之间提供了灵活的权衡。理解这些原理和机制对于高效地使用、配置、监控和调优Kafka集群至关重要。
2.KRaft架构原理
Kafka3.3.1引入了KRaft元数据管理组件,替代Zookeeper,以简化集群一致性维护,支持更大规模集群并减轻运维复杂性。在Zookeeper模式下,需同时运维ZK和Broker,而KRaft模式仅需3个节点即可构成最小生产集群,且通信协调基于Raft协议,增强了一致性。KRaft模式中,Controller使用单线程处理请求,通过KRaft保持内存状态与多节点一致性。此外,Broker根据KRaft记录更新元数据,实现声明式管理,提高集群协调效率。KRaft的引入是集群协调机制的演进,采用事件驱动模型实现元数据的一致性。如图所示:

●在Zookeeper(后面简称为ZK)模式下:
◎运维部署: 3个ZK节点;2..N个Broker节点,其中一个Broker承担Controller的角色。除了拉起一套最小生产的Kafka集群需要至少3 + N的资源外,Kafka的运维人员要同时掌握ZK和Kafka Broker两套完全不同的系统的运维方式。
◎通信协调: ZK节点之间通过ZAB协议进行一致性协调;Broker会通过ZK来选出一个Controller负责全局的协调,同时也会直接修改ZK里的数据;Controller也会监听和修改ZK里的数据,并调用Broker来完成集群的协调。虽然ZK之间的一致性由ZAB来保障了,但是ZK与Controller之间和Controller与Broker之间的一致性是相对比较脆弱的。
●在KRaft模式下:
◎运维部署: 3个Controller节点和N个Broker节点。Kafka节点可以同时承担Controller和 Broker两个角色,因此一套最小生产集群只需要3个节点。在测试环境更可以只以1节点模式就可以轻量地拉起一个Kafka集群。
**◎通信协调:**Controller节点底层通过Raft协议达成一致,Controller的内存状态通过#Replay Raft Log来构建,因此Controller之间的内存状态都是一致的;Broker订阅KRaft Log维护和Controller一致的内存状态,并且通过事件驱动的方式执行Partition Reassignment之类的操作来实现集群最终一致性协调。整个集群的状态维护和一致性协调都是基于KRaft中的事件。
3.Kafka架构原理

3.1核心角色与概念
●Producer(生产者): 将数据(消息)发布到Kafka主题。
●Broker(代理/服务器): Kafka集群中的一个节点,负责消息的接收、存储和投递。一个集群由多个Broker组成,实现负载均衡和容错。
●Topic(主题): 消息的逻辑分类单元。生产者发送消息到特定主题,消费者从特定主题订阅消息。
●Partition(分区): Kafka实现高吞吐和并行处理的核心机制。每个Topic被划分为一个或多个分区。分区在物理上对应一个文件夹(目录)。分区是Kafka并行处理的最小单位。Producer的消息会根据分区策略(如轮询、Key哈希)被路由到Topic的某个特定分区。分区内的消息是严格有序的(FIFO),并分配一个单调递增的偏移量。不同分区之间的消息顺序无法保证。分区可以跨多个Broker分布,实现Topic数据的分布式存储和负载均衡。每个分区可以有多个副本,实现高可用。
●Replica(副本): 每个分区的数据会被复制到多个Broker上,形成副本集,Replica副本数量不能大于Broker节点数量。副本分为两种角色:
◎Leader Replica(领导者副本): 每个分区在某一时刻只有一个Leader。负责处理该分区所有的读写请求(Producer写入和Consumer读取)。
◎Follower Replica(跟随者副本): 被动地、异步地从Leader副本拉取数据,进行同步。不处理客户端读写请求。Follower的唯一任务是与Leader保持同步。
副本机制提供了数据冗余和故障容错。如果Leader失效,Controller会从同步的Follower中选举出一个新的Leader。
●Consumer(消费者): 订阅一个或多个Topic,并从分区中拉取消息进行处理。
●Consumer Group(消费者组): Kafka实现并行消费和水平扩展的关键机制。多个Consumer实例可以组成一个Consumer Group。一个Consumer Group订阅一个Topic时,Topic的每个分区只会被分配给该Group内的一个Consumer实例消费。这实现了分区级别的负载均衡。
通过增加Group内的Consumer实例数量,可以提高消费的并行度(前提是分区数>=Consumer实例数)。不同Consumer Group订阅同一个Topic是相互独立的,各自维护自己的消费偏移量。
●Offset(偏移量): 每条消息在分区内唯一的、单调递增的标识(整数)。消费者通过记录和提交Offset来跟踪自己消费的位置。
●ZooKeeper/KRaft:
◎传统模式(依赖ZooKeeper),ZooKeeper负责:
①存储Broker注册信息(元数据)。
②选举Controller Broker(集群的大脑)。
③存储Topic配置、分区副本分配信息、ACLs等元数据。
④触发Leader选举(Controller依赖ZK监控Broker状态)。
⑤存储Consumer Group的Offset(早期版本,现代版本通常将Offset存储在内部的__consumer_offsets Topic中)。
◎KRaft模式(Kafka Raft): Kafka 2.8+开始引入,旨在完全移除对ZooKeeper的依赖。Controller角色本身通过Raft共识协议在多个Broker中选举产生并维护状态。元数据存储在内部的__cluster_metadata Topic中。KRaft简化了部署、提升了性能、增强了可扩展性和元数据一致性。
●Controller(控制器):
每个Kafka集群在某一时刻有且只有一个活跃的Controller Broker(由ZooKeeper或Raft选举产生)。
◎分区管理: 创建/删除Topic时,负责分区及其副本的分配(决定哪个Broker上的哪个副本是Leader,哪些是Follower)。
◎Leader选举: 监控Broker状态,当某个分区的Leader副本失效时,负责从该分区的ISR中选举出新的Leader。
◎副本状态管理: 维护每个分区的ISR列表。
◎元数据同步: 将集群的元数据变更(如Leader变更、ISR变更)通知给所有Broker。Controller是集群的"大脑",其高可用至关重要(通过ZK/Raft保证)。
●ISR(In-Sync Replicas-同步副本集): 指与Leader副本保持同步的副本集合(包括Leader自身)。一个副本被认为是同步的,需要满足:
◎与ZK/KRaft保持活跃会话(Broker在线)。
◎在过去的replica.lag.time.max.ms配置时间内,成功从Leader拉取到最新数据(即落后Leader的Offset差距很小)。
◎Leader选举范围:当Leader失效时,新的Leader必须且只能从当前的ISR中选举产生。这是Kafka保证数据一致性和不丢失已提交消息的关键机制(前提是生产者配置acks=all)。ISR是动态变化的。滞后或失效的副本会被Controller移出ISR,恢复同步后会重新加入。
3.2数据写入流程(Producer -> Broker)
①Producer指定消息的Key(可选)和Value,发送到指定的Topic。
②根据配置的分区策略(如round-robin,hash(key)),决定消息应发送到该Topic的哪个分区。
③连接到目标分区Leader副本所在的Broker。
④Broker收到消息后,将其顺序追加(Append)到该分区对应的日志文件(Segment File)末尾。
⑤根据Producer配置的acks级别进行响应:
acks=0: 发送即认为成功,不等待任何确认(可能丢失数据)。
acks=1: 仅等待Leader写入本地日志即返回成功(Leader失效可能丢失数据)。
**acks=all/acks=-1:**等待Leader收到消息,并且该消息被当前ISR中的所有副本都成功写入其本地日志后才返回成功(最强持久性保证,前提是ISR中至少有一个Follower存活)。
3.3数据读取流程(Consumer <- Broker)
①Consumer向Group Coordinator(某个Broker)提交Offset或请求分配分区。
②Group Coordinator进行Rebalance,将Topic的分区分配给Group内的Consumer实例。
③Consumer连接到其分配到的每个分区的Leader副本所在的Broker。
④Consumer指定要消费的分区以及起始Offset(通常从提交的Offset或最新/最早开始)。
⑤Broker从对应分区的日志文件中读取指定Offset范围的消息数据。
⑥Broker利用零拷贝(Zero-Copy)技术(如sendfile),将磁盘文件中的数据高效地通过网络发送给Consumer,避免内核态与用户态间的多次数据拷贝。
⑦Consumer处理消息,并定期(或处理完成后)提交新的Offset到Kafka(通常是内部的__consumer_offsets Topic)。
4.Kafka存储机制
Kafka的存储设计是其高性能和高吞吐量的基石,核心围绕分区(Partition)和段(Segment)展开。
4.1宏观存储结构图解(Topic -> Partition -> Segment)
我们从宏观到微观,层层分解来看:

●Topic(主题): 一个逻辑上的消息集合,例如order-events。
●Partition(分区): 每个Topic被分成一个或多个Partition。每个Partition是一个有序的、不可变的消息序列。在磁盘上,每个Partition对应一个文件夹(如order-events-0)。
**●Segment(段):**由于一个Partition的消息量可能非常大,Kafka又将其物理上切割成多个Segment文件。这样做的目的是为了方便旧数据的删除(直接删除整个文件即可)。
4.2微观存储结构图解(Segment内部)
让我们深入一个Partition文件夹,看看Segment文件是如何组织和工作的:
●文件命名: 所有Segment文件都以其第一条消息的Offset(位移)来命名。例如00000000000000000015.log这个文件包含了从offset 15开始的消息。
**●活跃Segment:**当前正在被写入的Segment称为活跃Segment。图中,...00000055.log是活跃Segment,新消息会追加到这里。当它达到一定大小(由log.segment.bytes配置,默认1GB)后,会滚动创建一个新的Segment。
4.3Segment内部详细图解
下图展示了一个.log文件及其对应的.index文件是如何协作的:

查找过程详解(假设要读取offset=52的消息):
①确定Segment: 首先根据文件名,确定offset=52的消息位于00000000000000000015.log这个Segment中(因为15 <= 52 < 55:下一个Segment的起始offset)。
②查询索引: 在00000000000000000015.index文件中,进行二分查找,找到小于等于52的最大索引条目。这里找到的是(40,2048)。
③定位文件位置: 索引告诉我们,从物理位置2048开始扫描。
**④顺序扫描:**从.log文件的2048位置开始,顺序向后扫描(因为消息是顺序存储的,非常快),直到找到offset=52的消息。
4.3.1为什么使用稀疏索引?
如果为每条消息都建立索引,索引文件会非常大,失去意义。稀疏索引只间隔一部分消息建立索引,虽然找到位置后需要一次小小的顺序扫描,但极大地节约了索引空间和内存,是时间和空间的一个绝佳权衡。
4.3.2消息的物理结构
即使在.log文件中,消息也不是简单的字符串,而是遵循特定的二进制格式:

●批量存储: 现代Kafka将多条消息打包成一个Message Set或Record Batch进行存储,减少了网络和磁盘I/O开销。
●关键字段:
◎Offset(偏移量): 消息的唯一ID。
◎Message Size(消息体大小): 消息体大小。
◎CRC(循环冗余校验): 用于校验消息完整性。
◎Key&Value(键值): 我们实际存储的业务数据。
**◎Attributes(编码压缩):**包含压缩、时间戳类型等元数据。
4.3.3日志清理与压缩
Kafka提供了两种日志清理策略,由log.cleanup.policy控制:
●Log Retention(日志保留): 基于时间或大小的删除。
◎机制: 后台线程定期检查非活跃的Segment文件。如果其最后修改时间超过了log.retention.hours设定的值,或者整个Partition的日志大小超过了log.retention.bytes,则直接删除整个旧的.log和.index文件。
◎图解: 直接删除00000000000000000000.*这一组文件。
●Log Compaction(日志压缩): 为每个Key保留最新的Value。
◎机制: 后台线程会重写Segment文件,对于有相同Key的消息,只保留那个offset最大的(即最新的)消息。这保证了对于每个Key,至少有一条最新状态的消息。
**◎适用场景:**用于恢复应用程序状态,如数据库的changelog。
5.Kafka存储机制的核心设计思想
|-------|---------------------------------|-----------------------------------------|
| 设计思想 | 实现方式 | 带来的好处 |
| 顺序读写 | 只在日志文件末尾追加消息 | 充分利用磁盘顺序I/O的高性能,避免随机寻址的慢速操作 |
| 分区与并行 | 将Topic划分为多个Partition | 实现水平扩展,不同的Broker 可以托管不同的Partition,提升吞吐量 |
| 分段存储 | 将Partition物理切割成多个Segment | 方便旧数据的清理(直接删除文件),同时避免单个文件过大 |
| 稀疏索引 | 为每个Segment建立.index和.timeindex文件 | 以极小的索引空间开销,实现了消息的近似O(1)时间复杂度的查找 |
| 页缓存 | 直接利用操作系统的Page Cache来缓存数据 | 避免在JVM中维护缓存,减少GC压力;同时可以利用操作系统的高效内存管理 |
| 零拷贝 | 使用sendfile系统调用从页缓存直接发送到网卡 | 减少数据在内核态和用户态之间的拷贝次数,极大提升消费端的网络传输效率 |
参考文献:
Kafka官网https://kafka.apache.org/documentation/#gettingStarted