4. Kafka深度解析:架构设计、副本机制、ISR与Leader选举、日志存储原理

各位同学,今天我们来啃一块硬骨头——Kafka。说实话,我在做消息中间件选型的时候,Kafka几乎是我绕不开的选项。它不像RabbitMQ那样轻巧,也不像RocketMQ那样功能全面,但它在高吞吐场景下的表现,确实让人服气。

这一章我会从四个核心维度来拆解Kafka:架构设计、副本机制、ISR与Leader选举、日志存储原理。嗯,每个点都是面试高频,也是生产环境踩坑的重灾区。

4.1 架构设计:Kafka到底长什么样?

先看一张我手绘的架构图,帮你建立整体认知。

Producer 生产者集群 推送 Kafka Cluster Broker 1 Topic-A P0(Leader) Topic-B P1(Follower) Topic-C P0(Leader) Broker 2 Topic-A P1(Follower) Topic-B P0(Leader) Topic-C P1(Follower) Broker 3 Topic-A P2(Follower) Topic-B P2(Follower) Topic-C P0(Leader) ZooKeeper(元数据管理 / Controller选举) 拉取 Consumer 消费者集群

这张图其实已经说清楚了Kafka的核心架构。Producer把消息推给Broker,Consumer从Broker拉取消息。中间那个ZooKeeper,负责做元数据管理和Controller选举。

我个人习惯把Kafka的架构总结成三句话:

  • Producer:只管推,不管谁消费。它只关心消息能不能落到Leader分区。
  • Broker:集群里的每个节点都是一个Broker。一个Topic被拆成多个Partition,分布在不同的Broker上。
  • Consumer:按组消费,一个分区只能被同一个消费组里的一个消费者消费。
💡 我的经验: 刚开始接触Kafka时,我总把Broker和Partition搞混。记住一句话:Broker是物理节点,Partition是逻辑分区。一个Broker上可以挂多个Partition,但一个Partition只能属于一个Broker。

4.2 副本机制:数据安全的第一道防线

Kafka的副本机制,说白了就是给每个Partition做备份。Leader负责读写,Follower只负责同步。为什么这么设计?你想想看,如果所有副本都能写,那数据一致性就乱套了。

副本配置通过 replication.factor 控制。我建议生产环境至少设成3,别省这个资源。我曾经见过一个项目,为了省磁盘把副本数设成1,结果Broker宕机直接丢数据,那叫一个惨。

# 创建Topic时指定副本数
bin/kafka-topics.sh --create \
  --bootstrap-server localhost:9092 \
  --replication-factor 3 \
  --partitions 6 \
  --topic my-topic

副本的同步流程是这样的:

  1. Producer把消息写到Leader分区。
  2. Leader把消息追加到本地日志。
  3. Follower从Leader拉取消息,写入自己的日志。
  4. Follower向Leader发送ACK,确认同步完成。

这里有个关键点:Follower只拉取,不参与读写。Leader挂了,Follower才有机会上位。

⚠️ 避坑指南: 我曾经在生产环境遇到过一个问题——副本同步延迟过高。排查后发现是网络带宽打满了。Follower拉取数据也是要占带宽的,副本数越多,网络开销越大。所以副本数不是越多越好,3个通常是最优解。

4.3 ISR与Leader选举:谁说了算?

ISR的全称是In-Sync Replicas,翻译过来就是「同步中的副本集合」。说白了,ISR里装的是那些跟Leader保持同步的Follower。

为什么要有ISR?因为不是所有Follower都能跟上Leader的节奏。有些Follower网络延迟高,有些磁盘IO慢,它们可能落后Leader一大截。如果让这些「掉队」的Follower参与Leader选举,那数据一致性就崩了。

ISR的判断标准由两个参数控制:

参数 默认值 说明
replica.lag.time.max.ms 30000 Follower超过30秒没拉取消息,就被踢出ISR
replica.lag.max.messages 4000 (旧版本)Follower落后超过4000条消息,就被踢出ISR

嗯,这里要注意一下。新版本的Kafka已经废弃了 replica.lag.max.messages,只保留时间维度。为什么?因为消息条数这个指标太不稳定了。大消息和小消息的延迟完全不一样,用时间更合理。

Leader选举的逻辑是这样的:

  • 如果Leader挂了,Controller从ISR里选一个Follower当新Leader。
  • 如果ISR为空(所有副本都挂了),那就看 unclean.leader.election.enable 配置。
  • 这个配置默认是false,宁可不提供服务,也不丢数据。
  • 如果设为true,就会从非ISR的副本里选一个当Leader,但会丢数据。

🔑 核心要点: ISR机制是Kafka保证数据一致性的关键。它确保只有「跟得上节奏」的副本才有资格成为Leader。我个人建议永远不要开启 unclean.leader.election.enable,数据一致性比可用性更重要。

4.4 日志存储原理:消息到底存在哪?

Kafka的日志存储,很多人觉得神秘。其实说白了,就是一堆文件。

每个Partition对应一个目录,目录名是 topic-partition 格式。比如 my-topic-0my-topic-1。目录里放的是Segment文件,每个Segment由两个文件组成:

  • .log 文件:存消息内容。
  • .index 文件:存消息的偏移量索引。

Segment的命名规则是:以Segment里第一条消息的偏移量命名。比如 00000000000000000000.log 表示偏移量从0开始。

日志的写入流程是这样的:

// 伪代码示意
public void append(LogEntry entry) {
    // 1. 获取当前活跃的Segment
    Segment activeSegment = getActiveSegment();
    
    // 2. 追加消息到.log文件
    long offset = activeSegment.append(entry);
    
    // 3. 更新.index文件
    activeSegment.index.append(offset, position);
    
    // 4. 如果Segment满了,滚动创建新Segment
    if (activeSegment.isFull()) {
        rollNewSegment();
    }
}

Kafka的日志清理有两种策略:

策略 配置 说明
删除(delete) log.cleanup.policy=delete 超过保留时间或大小的消息会被删除
压缩(compact) log.cleanup.policy=compact 保留每个Key的最新一条消息,删除旧版本

我遇到过最典型的坑是日志清理不及时导致磁盘写满。有一次线上告警,Kafka Broker的磁盘使用率飙到95%。查了半天,发现是 log.retention.hours 设成了168小时(7天),但业务量太大,7天的数据量远超磁盘容量。

💡 调优建议: 日志保留时间要根据业务和数据量来算。我一般建议先设成24小时,观察磁盘使用率,再逐步调整。另外,log.segment.bytes 默认是1GB,如果消息量不大,可以调小到512MB,这样清理更频繁,磁盘空间释放更快。

还有一个细节:Kafka的日志是顺序写的。为什么Kafka吞吐量高?就是因为顺序写磁盘比随机写快得多。机械硬盘的顺序写速度能到100MB/s以上,而随机写可能只有几MB/s。这个设计思路,说白了就是「用硬件特性来优化软件性能」。

好了,这一章的内容就到这里。Kafka的架构设计、副本机制、ISR与Leader选举、日志存储原理,每个点都值得深入理解。下次遇到Kafka性能问题,你至少知道该从哪里入手了。

专注资料整理