05. Kafka 集群与高可用架构

Kafka Broker 角色、Controller 选举机制、ISR 副本同步、Leader 故障转移与 KRaft 模式下的无 ZooKeeper 架构。

1. Kafka 集群核心组件

1.1 架构演进:ZooKeeper → KRaft

Kafka < 3.0(依赖 ZooKeeper):

  ZooKeeper Ensemble (3/5 节点)
    ├── 存储元数据(Broker、Topic、Partition)
    ├── 管理 Controller 选举
    └── 维护 ISR 列表

Kafka ≥ 3.3(KRaft 模式,推荐):

  无 ZooKeeper
    ├── Controller 节点(Quorum,3/5 节点)
    │     ├── 自管理元数据
    │     ├── 使用 Raft 协议选举
    │     └── 存储在内部 topic (__cluster_metadata)
    └── Broker 节点(纯数据节点)
          └── 更轻量,专注于消息存储

KRaft 消除了 ZooKeeper 运维负担,简化了部署,同时减少了元数据传播延迟。从 ZooKeeper 向 KRaft 迁移时,需要经历一个元数据同步阶段:Broker 先以 KRaft 模式启动新的 Controller 集群,逐步将 ZooKeeper 中的 Topic 元数据、ACL、配置等信息复制到内部的 __cluster_metadata 分区中。官方提供了 kafka-storage.sh 工具完成元数据初始化。迁移完成后,所有 Broker 会向新的 Controller Quorum 注册,ZooKeeper 方可下线。生产环境中建议先在测试集群验证完整迁移流程,并保留 ZooKeeper 一段时间作为回退方案。

1.2 元数据管理演进

在 ZooKeeper 时代,元数据以 ZNode 形式存储:/brokers/ids、/brokers/topics、/config 等路径分别记录 Broker 列表、Topic 分区和动态配置。Controller 通过 Watch 机制监听这些节点的变化,任何元数据变更都需要写入 ZooKeeper(约 10ms 级别延迟)。当 Topic 数量超过 20 万时,ZooKeeper 的事务日志和快照文件会成为瓶颈,导致 Controller 启动时间显著增加。

KRaft 模式下,元数据被组织为连续的日志流(Raft Log),存储在 __cluster_metadata 这个内部 Topic 中。该 Topic 只有一个分区(Partition 0),所有 Controller 节点共同维护这个分区的副本。Raft Leader 负责处理元数据的写入请求,Follower 异步复制日志。当 Follower 追上 Leader 后,即可参与选举。由于元数据变更不再需要跨系统的远程写入,Controller 的响应延迟从毫秒级降低到微秒级,支持的 Partition 上限也提升至数百万级别。

ZooKeeper 元数据路径 vs KRaft 内部 Topic:

ZooKeeper:                          KRaft (__cluster_metadata):
├── /brokers/ids/0                  │  Record 1: RegisterBrokerRecord (node.id=0)
├── /brokers/topics/my-topic        │  Record 2: TopicRecord (name=my-topic)
│   └── /partitions/0               │  Record 3: PartitionRecord (topic=my-topic, partition=0)
│       └── /state                  │  Record 4: LeaderAndIsrRecord (leader=0, isr=[0,1,2])
└── /config/topics/my-topic         │  Record 5: ConfigRecord (resource=topic, name=my-topic)
                                    │  ...
                                    └─ 所有变更作为有序日志记录,通过 Raft 协议复制
角色Kafka < 3.0Kafka ≥ 3.3 KRaft
Controller由 ZooKeeper 选举的特殊 Broker独立的 Controller 节点(Quorum)
Broker存储数据 + 可能兼任 Controller纯数据存储
Quorum Leader—Controller 集群的 Leader

2. 副本机制(Replication)

2.1 副本同步原理

Kafka 的副本同步采用拉取(Pull)模式:Follower 副本主动从 Leader 副本拉取消息,而非 Leader 主动推送。这种设计降低了 Leader 的广播压力,使 Follower 可以根据自身处理能力调节同步速率。

每个 Broker 内部运行独立的 Fetcher 线程,每个 Follower 副本对应一个 Fetcher 线程。当 Broker 宕机或网络抖动时,这些线程会重试拉取,不会阻塞 Leader 的写入路径。

Leader (Broker 101)                    Follower (Broker 102)
┌─────────────────┐                   ┌─────────────────┐
│ Log 文件        │                   │ Log 文件        │
│ offset 0..5000  │←── Fetch请求 ───│  offset 0..4800 │
│ offset 4800     │   (请求 offset   │  offset 4801    │ ← 请求从 offset 4801 开始
│ offset 4801     │    4801+ 的      │                 │    拉取后续消息
│ offset 4802     │    消息)         │                 │
│      ...        │─── 返回消息 ───→│                 │
│ offset 5000     │                   │ offset 5000     │ ← 同步完成后 LEO 追齐
└─────────────────┘                   └─────────────────┘

同步的关键水位线(Offset):

术语全称说明
LEOLog End OffsetLeader/Follower 各自的最新消息偏移量,每个副本独立维护
HWHigh Watermark所有 ISR 副本均已同步的最大偏移量。消费者对 HW 之后的消息不可见。
Last Stable Offset最后一个稳定偏移量HW 的保守版本,保证事务性。消费者默认读取到此位置。

Kafka 副本同步工作流程:

  1. Producer 发送消息到 Leader,Leader 追加到本地 Log,LEO 增加
  2. Leader 更新 HW(所有 ISR 副本中最小的 LEO)
  3. Follower 定期(默认 500ms)发送 Fetch 请求获取新消息
  4. Follower 成功写入后,在下次 Fetch 的请求参数中告知 Leader 自己的新 LEO
  5. Leader 收到所有 ISR 副本的 LEO 更新后,推进 HW
  6. 只有 HW 之前的消息才对消费者可见,Leader 宕机时 HW 之后的消息可能丢失

2.2 ISR(In-Sync Replicas)详解

Topic: orders (Partition 0, replication.factor=3)

  Leader (Broker 101)  ← 处理所有读写请求
    │
    ├──→ Follower 1 (Broker 102)  ← ISR 成员
    │       实时同步 Leader HW(High Watermark)
    │
    └──→ Follower 2 (Broker 103)  ← ISR 成员
            实时同步 Leader HW

ISR = {101, 102, 103}

如果 Follower 2 网络延迟过大:
  ISR = {101, 102}  (Broker 103 被踢出 ISR)
  OSR = {103}       (Out-of-Sync Replica)

关键参数:

参数默认值说明
replica.lag.time.max.ms30000超出此时间未同步,踢出 ISR
replica.lag.max.messages4000(已废弃)旧版按消息数判定
min.insync.replicas1生产者 acks=all 时的最小同步副本数

2.3 数据可靠性公式

生产者配置:acks=all + min.insync.replicas=2

数据丢失条件:
  - Leader 写入成功并复制给 ≥2 个 ISR
  - 同时这 2 个副本都宕机(概率极低)
  
实际保障:
  - 单副本宕机:无影响(剩余 ISR 仍有副本)
  - ISR 中只剩 Leader:写入拒绝(ISR < min.insync.replicas)
  - 这是一种"宁可不可用,也不丢数据"的设计

生产者 acks 配置对可靠性的影响:

acks 值行为数据丢失概率延迟适用场景
0Leader 收到即返回,不确认写入高(Leader 宕机即丢失)最低日志收集,允许丢失
1Leader 写入本地磁盘后返回中(Leader 宕机且未同步)低常规业务,基本可靠
-1 / all所有 ISR 副本确认后返回极低较高支付、订单等关键业务

需要特别注意的是,acks=all 配合 min.insync.replicas=2 是生产安全的黄金配置。即使 acks=all,如果 min.insync.replicas=1(默认),当 ISR 中只剩 Leader 时仍然能写入,此时若 Leader 宕机,数据将丢失。因此两个参数必须同时调整。

端到端消息顺序保障:

Kafka 在同一 Partition 内保证消息顺序。但顺序可能因重试和超时被打乱:

问题场景(max.in.flight.requests.per.connection > 1):
消息 1 → Leader 写入成功,但网络超时,Producer 重试
消息 2 → 写入成功,确认到达
消息 1(重试)→ 再次写入,排在消息 2 之后 → 顺序错乱!

解决方案:
- 设置 max.in.flight.requests.per.connection = 1(牺牲吞吐量)
- 或启用幂等性(enable.idempotence=true),配合 Kafka 2.5+ 提供的排序保证

幂等性(Idempotence)与事务(Transactions):

幂等性 Producer(exactly-once for single-partition):
  enable.idempotence=true
  ├── 每个 Producer 实例分配唯一 PID(Producer ID)
  ├── 每条消息序列号单调递增
  └── Broker 去重:拒绝重复序列号

事务 Producer(exactly-once for multi-partition):
  transactional.id=unique-id
  ├── beginTransaction() → send() → commitTransaction()
  ├── 原子性:多 Partition 写入全部成功或全部失败
  └── Consumer 配合 read_committed 隔离级别,忽略未提交消息

2.4 副本恢复与追赶(Catch-up)

当 Follower 因网络故障、Broker 宕机或 GC 停顿被移出 ISR 后,一旦恢复连接,它需要追赶(Catch-up)Leader 的日志。追赶过程不是简单地复制所有落后消息,而是使用 Kafka 的Replica Fetcher机制渐进式同步。

Follower 恢复后的追赶流程:

1. Follower 重启/网络恢复
2. 向 Leader 发送 Fetch 请求,附带自己的 LEO
3. Leader 比较后发送缺失的消息段(Segment)
4. Follower 将消息写入本地 Log,更新本地 LEO
5. 重复 2-4 直到 Follower 的 LEO 与 Leader 的 LEO 接近
6. Controller 检测到 Follower 满足 replica.lag.time.max.ms
7. Follower 重新加入 ISR 列表
8. Leader 更新 HW,消费者可见性恢复

在极端情况下(Follower 落后多个 Segment 文件),如果 Leader 的日志已被定期清理(log retention),Follower 可能无法直接通过 Fetch 追上。此时需要:

  • 检查 log.cleaner.enable 配置,避免过早清理
  • 或通过分区重分配(reassign partitions)将该副本的数据从其他完整副本复制过来

ISR 频繁缩扩的排查:

现象根因解决
ISR 反复缩小又恢复Follower GC 停顿,超过 replica.lag.time.max.ms增大 replica.lag.time.max.ms 或优化 GC
ISR 中 Follower 长期滞后网络带宽不足或磁盘 I/O 瓶颈升级网络/磁盘,或降低 num.replica.fetchers
ISR 持续收缩不恢复副本磁盘损坏或文件系统异常替换故障磁盘,从其他副本同步

优选副本(Preferred Replica)与 Leader 均衡:

Kafka 为每个 Partition 维护一个Preferred Replica列表——这是 Topic 创建时由 Broker 列表顺序决定的副本分配方案。例如创建 Topic 时 replica-assignment=0:1:2,则 Preferred Leader = Broker 0。

# 自动将 Leader 恢复到 Preferred Replica
auto.leader.rebalance.enable=true

# 检查周期(默认 300s)
leader.imbalance.check.interval.seconds=300

# 允许 Leader 不均衡的阈值(默认 10%)
leader.imbalance.per.broker.percentage=10

当节点宕机恢复后,如果不开启自动均衡,Leader 可能集中在少数 Broker 上,造成负载不均。建议在生产环境开启此功能,但注意均衡过程会触发短暂的 Leader 切换,需评估业务影响。


3. Leader 选举与故障转移

3.1 Leader 选举流程

场景:Broker 101(Leader)宕机

1. Controller 检测到 Broker 101 离线(通过心跳超时)
2. Controller 读取 Partition 0 的 ISR 列表:{101, 102, 103}
3. 从 ISR 中选最同步的 Follower 作为新 Leader(优先 ISR 中 LEO 最大的)
4. 更新元数据,通知所有 Broker 新 Leader
5. Producer/Consumer 自动从 Broker 101 切换到新 Leader(无需人工干预)

时间开销:
  - 检测:zookeeper.session.timeout.ms(默认 18s)
  - 选举:毫秒级
  - 切换:客户端自动重连

3.2 Unclean Leader Election

配置:unclean.leader.election.enable

false(默认/推荐):
  - 只有 ISR 中的副本能当选 Leader
  - 如果 ISR 全挂 → 该 Partition 不可用 → 数据安全

true(不推荐):
  - OSR 副本也能当选 Leader
  - 可能丢失已确认写入的消息
  - 适用:允许数据丢失、追求可用性的场景

4. KRaft 模式详解

4.1 为什么移除 ZooKeeper

ZooKeeper 作为分布式协调服务在 Kafka 的架构中扮演了核心角色超过十年,但随着 Kafka 集群规模的扩大和业务需求的演进,其局限性日益凸显。KRaft(Kafka Raft)模式的设计初衷正是解决这些根本性问题。

首先,运维复杂度。Kafka 集群需要维护两套独立系统:Kafka Broker 和 ZooKeeper Ensemble。这意味着两套不同的配置文件、两套监控告警、两套升级流程和两套故障排查路径。当 ZooKeeper 发生问题时,需要具备两种不同技术栈的运维能力。

其次,性能瓶颈。元数据的每一次变更(创建 Topic、修改分区副本、更新配置)都需要写入 ZooKeeper 的 Leader 节点,然后同步到多数 Follower。单次写入延迟约 5-15ms,在高频变更场景(如自动 Topic 创建、动态配置调整)会成为瓶颈。

第三,一致性问题。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议保证一致性,但在网络分区场景下可能出现脑裂。虽然 Kafka 通过 fencing 机制降低了风险,但 ZK 的会话超时机制(默认 18s)意味着 Controller 故障检测不够灵敏。

第四,扩展上限。每个 ZNode 存储有限制,大量 Topic(超过 20 万)和 Partition 会导致 ZooKeeper 内存消耗剧增、快照文件膨胀,进而影响启动速度和稳定性。

KRaft 通过将元数据管理内化到 Kafka 自身来解决上述问题:

ZooKeeper 的问题KRaft 的解决量化收益
多系统运维(Kafka + ZK)单一系统,单一部署流程运维成本降低约 40%
元数据变更需 ZK 写入(~10ms)内存元数据,Raft 日志复制变更延迟降低至 <1ms
ZK 脑裂风险Raft 协议保证强一致网络分区下无元数据不一致
Controller failover 慢(18s)毫秒级元数据恢复故障恢复时间降低 99%
Topic 数量限制(~20万)可支持数百万级 Partition扩展能力提升 10-50 倍
代码维护消除 ZK 客户端依赖(2000+ 行代码)核心代码更精简
ZooKeeper 的问题KRaft 的解决
多系统运维(Kafka + ZK)单一系统,简化部署
元数据变更需 ZK 写入(~10ms)内存元数据,变更更快
ZK 脑裂风险Raft 协议保证一致
Controller failover 慢更快的元数据恢复
Topic 数量限制(~20万因 ZK 限制)可支持百万级 Partition

4.2 KRaft 共识协议:Quorum Controller

KRaft 使用基于 Raft 的Quorum Controller集群来管理元数据。Raft 是一种强一致性共识协议,其核心概念与 ZooKeeper 的 ZAB 类似但有重要区别:

Raft 三状态模型(每个 Controller 节点):

 ┌──────────┐
 │ Follower │ ← 初始状态,接收 Leader 日志
 └────┬─────┘
      │  选举超时(随机 150-300ms)
      ▼
 ┌──────────┐    获得多数票     ┌──────────┐
 │ Candidate│ ───────────────→ │  Leader  │
 └──────────┘                 └────┬─────┘
      ↑                            │ 日志复制
      └────────────────────────────┘

Leader 职责:
  - 接收所有元数据变更请求(CreateTopic、DeleteTopic、ReassignPartition 等)
  - 将变更追加本地日志,并同步给 Follower
  - 当多数节点(Quorum)确认后,提交日志
  - 通知 Broker 元数据变更

Follower 职责:
  - 接收并持久化 Leader 的日志条目
  - 参与选举投票
  - 不处理客户端请求(只读元数据)

Quorum 配置详解:

controller.quorum.voters 定义了 Quorum 的所有成员,格式为 id@host:port。

Quorum 成员数可容忍故障数最小推荐
31✅ 生产最小配置
52✅ 中大型集群
73超大规模,极少需要

Quorum 的 Leader 选举需要超过半数(majority)的投票。3 节点的 Quorum 需要至少 2 票,因此 5 节点的 Quorum 比 3 节点的可靠性更高,但写入延迟也更高(需要等待更多节点确认)。对于绝大多数 Kafka 集群,3 节点的 Quorum 足矣。

KRaft 元数据快照(Snapshot)机制:

随着集群运行,__cluster_metadata 的日志条目不断增长。为了避免重启时从头重放所有日志,Raft 会定期生成快照:

快照内容:
  ├─ 当前所有 Topic 的元数据(名称、分区数、副本分配)
  ├─ 所有 Partition 的 ISR、Leader 信息
  ├─ 消费者组的偏移量(offset)
  ├─ ACL(访问控制列表)
  ├─ 动态配置(Broker 级、Topic 级、客户端级)
  └─ 元数据版本号

快照触发条件:
  - 日志条目数超过 snapshot.interval.ms(默认 1 小时)
  - 日志大小超过阈值
  - 主动触发 API(admin 工具)

快照将内存中的状态序列化为文件(存于 metadata.log),Follower 加入集群时直接从最新快照恢复,无需处理历史日志。

4.3 KRaft 部署配置

# controller.properties(独立 Controller 节点)
process.roles=controller
node.id=1
controller.quorum.voters=1@controller-1:9093,2@controller-2:9093,3@controller-3:9093
listeners=CONTROLLER://0.0.0.0:9093
log.dirs=/var/lib/kafka/controller-logs

# broker.properties(Broker 节点)
process.roles=broker
node.id=101
controller.quorum.voters=1@controller-1:9093,2@controller-2:9093,3@controller-3:9093
listeners=BROKER://0.0.0.0:9092
inter.broker.listener.name=BROKER
log.dirs=/var/lib/kafka/data

初始化集群:

# 1. 生成唯一集群 ID
cluster_id=$(kafka-storage.sh random-uuid)

# 2. 格式化每个节点的存储
kafka-storage.sh format -t $cluster_id -c config/controller.properties

# 3. 启动 Controller(3 台分别执行)
kafka-server-start.sh config/controller.properties

# 4. 启动 Broker
kafka-server-start.sh config/broker.properties

混合模式(Combined)与分离模式(Dedicated):

模式配置适用场景优点缺点
Combinedprocess.roles=broker,controller开发测试、小型集群部署简单、资源利用率高Controller 与 Broker 竞争资源
Dedicatedprocess.roles=controller / broker生产环境资源隔离、稳定性高需要更多节点

生产环境强烈建议使用分离模式。Controller 节点仅处理元数据请求,不需要大容量磁盘,但要求低磁盘延迟和稳定网络。通常 2-4 核 CPU、8GB 内存、SSD 即可满足绝大多数 Controller 需求。


5. 集群高可用最佳实践

5.1 部署架构

推荐生产部署:

跨可用区(AZ)部署:
  AZ-A: Broker 1, Broker 2
  AZ-B: Broker 3, Broker 4
  AZ-C: Broker 5, Broker 6

副本分布策略:
  - replica 0(Leader):AZ-A
  - replica 1(Follower):AZ-B
  - replica 2(Follower):AZ-C
  
  可用区故障时:
    AZ-A 故障 → Leader 漂移到 AZ-B 或 AZ-C
    单 AZ 故障不影响数据可用性

KRaft Controller:
  - 独立 3 台机器(或轻量 VM)
  - 与 Broker 分离,避免资源争抢

副本分布算法(Rack Awareness):

Kafka 使用 Rack Awareness 策略确保副本不会集中在同一个机架或可用区。配置方式:

# broker.properties 中指定 Broker 所在机架
broker.rack=AZ-A

# Topic 创建时自动将副本分散到不同机架

当 broker.rack 被配置后,Partition 分配会优先选择不同机架的 Broker 作为副本。例如 3 副本情况下,3 个副本分别位于 3 个不同机架,即使整个机架断电也不丢失数据。

Broker 机架分配示例:

AZBroker IDbroker.rack
AZ-A1, 2rack-a
AZ-B3, 4rack-b
AZ-C5, 6rack-c

创建 Topic 时,Controller 使用 Round-Robin 算法在机架间均匀分配 Leader 和 Follower,单 AZ 故障时最多影响 1/3 的副本。

5.2 KRaft 迁移实践

从 ZooKeeper 迁移到 KRaft 是 Kafka 3.x 的重要演进方向。官方在 Kafka 3.6+ 提供了 KRaft Migration 工具,支持在线滚动迁移:

迁移阶段(无需停机):

阶段一:准备
  - 升级 Kafka 到 3.6+(保持 ZK 模式运行)
  - 准备 KRaft Controller 节点(安装同版本 Kafka)
  - 使用 kafka-storage.sh 生成迁移元数据

阶段二:双写
  - 配置 `zookeeper.metadata.migration.enable=true`
  - 启动 KRaft Controller
  - 元数据同时写入 ZK 和 KRaft(双写期)
  - 验证 KRaft Controller 元数据一致性

阶段三:切换
  - 更新 Broker 配置,指向 KRaft Controller
  - Broker 逐个滚动重启
  - 关闭 ZK 写路径
  - 切为 KRaft 单写模式

阶段四:下线 ZK
  - 确认所有 Broker 正常运行
  - 关闭 ZooKeeper 集群

迁移注意事项:

  • 回退窗口:迁移前 72 小时内保留 ZooKeeper 快照,验证不通过可回退
  • ACL 兼容性:迁移前检查 ZK 中的 Kafka ACL 是否完整迁移到 KRaft
  • 分区重分配:迁移过程中避免执行分区重分配操作
  • 监控双写延迟:双写期的额外写入开销可能影响元数据变更延迟

5.3 关键监控指标

指标JMX MBean告警阈值含义
Under Replicated Partitionskafka.server:type=ReplicaManager,name=UnderReplicatedPartitions> 0存在未同步副本,ISR 已缩小
Offline Partitionskafka.controller:type=KafkaController,name=OfflinePartitionsCount> 0无可用 Leader 的分区数
Active Controller Countkafka.controller:type=KafkaController,name=ActiveControllerCount!= 1当前集群中活跃的 Controller 数量(应为 1)
Leader Election Ratekafka.controller:type=ControllerStats,name=LeaderElectionRateAndTimeMs突增Leader 选举频率(突增说明不稳定)
Request Latencykafka.network:type=RequestMetrics,name=TotalTimeMsP99 > 50ms请求处理延迟
Log Flush Latencykafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMsP99 > 100ms日志刷盘延迟
ISR Expand/Shrink Ratekafka.server:type=ReplicaManager,name=IsrExpandsPerSec频繁收缩ISR 成员变动率

Under Replica Partitions 排查流程:

1. jconsole / Kafka Exporter 查看 UnderReplicatedPartitions 数量
   ↓
2. 若 > 0,找出受影响的分区:
   kafka-topics.sh --describe --topic <topic>
   检查 Replica List 中是否有 Insync Replica 少于 replica.factor
   ↓
3. 定位滞后 Broker:
   查看 .LogEndOffset 和 CurrentOffset 差异大的副本
   ↓
4. 检查滞后根因:
   - 网络延迟(ping / iperf)
   - 磁盘 I/O(iostat -x 1)
   - Follower 日志目录空间(df -h)
   - Broker CPU / GC 停顿(jstat -gc)
   ↓
5. 恢复措施:
   - 临时增大 replica.lag.time.max.ms(治标)
   - 扩容磁盘 / 网络 / 内存(治本)
   - 必要时重新分配分区副本

5.4 灾难恢复方案

生产环境的 Kafka 集群面临多种故障场景,需要预设灾难恢复(DR)预案。

场景一:单 Broker 宕机

影响范围:
  - 该 Broker 上作为 Leader 的分区无法写入(自动切换到 ISR 中的其他副本)
  - 该 Broker 上作为 Follower 的分区副本缺失(ISR 缩小)

恢复步骤:
  1. 自动故障检测(Controller 检测到 Broker 会话超时)
  2. Leader 选举触发(从 ISR 中选新 Leader)
  3. Producer/Consumer 自动重定向(通过 Metadata 请求更新 Leader 信息)
  4. 运维修复 Broker 硬件/重启服务
  5. Broker 重新加入集群,开始副本追赶(Catch-up)
  6. Controller 将恢复的副本重新加入 ISR
  7. Preferred Leader Rebalance(将 Leader 移回 Preferred Replica)

恢复时间(Recovery Time Objective, RTO):

  • 检测时间:~18s(ZK 模式)/ ~5s(KRaft 模式,Raft 心跳)
  • Leader 选举:< 1s
  • 客户端重连:< 5s
  • 总计 RTO:约 25s

场景二:整 AZ/机房故障

3 副本 + Rack Awareness 下的容灾:

正常状态:
  AZ-A (Broker 1,2)  AZ-B (Broker 3,4)  AZ-C (Broker 5,6)
  ├── Leader (副本 0) ├── Follower (副本 1) ├── Follower (副本 2)

AZ-A 完全故障:
  ├── 宕机             ├── 可用              ├── 可用
  └── 该分区的 Leader 丢失 → Controller 选举 AZ-B 或 AZ-C 的新 Leader
      → 写入可继续,但副本因子从 3 降为 2
      → 如果 AZ-A 永久丢失,需要从剩余副本重新创建第 3 个副本

应对:
  - 若 AZ 恢复时间短:等待 AZ 恢复,副本自动追赶
  - 若 AZ 永久丢失:将 Survivor 副本重新分配到新 AZ 的 Broker

场景三:元数据完全损坏

灾难级别:所有 Controller 节点磁盘损坏(极端罕见)

恢复方案:
  1. 从最近的 __cluster_metadata 快照恢复
  2. 若无快照,从其他健康的 Broker 导出 Topic 元数据
  3. 重建 Controller Quorum(格式化新存储,指定旧 cluster id)
  4. 重新创建 Topic、Partition、ACL(从备份或脚本恢复)
  5. 消费者偏移量可能丢失,消费者需从头消费或手动重置

预防措施:
  - Controller 数据目录定期备份(rsync 到远端)
  - 使用 kafka-reassign-partitions.sh 提前规划副本分配脚本
  - ZooKeeper/KRaft 的定期快照外部存储

5.5 集群扩容与缩容

随着业务增长,Kafka 集群需要平滑扩容(增加 Broker)或缩容(下线旧 Broker)。

扩容流程:

1. 准备新 Broker 节点
   - 安装 Kafka,配置唯一的 node.id(KRaft 模式)或 broker.id(ZK 模式)
   - 配置相同的集群地址(controller.quorum.voters)
   - 启动 Broker,向 Controller 注册

2. 新 Broker 加入后状态
   - 不承载任何现有分区的 Leader 或 Follower
   - 可被分配新 Topic 的分区

3. 将现有分区迁移到新 Broker
   kafka-reassign-partitions.sh 脚本:
   
   # 生成重分配 JSON
   cat > reassign.json << 'EOF'
   {
     "version": 1,
     "partitions": [
       {"topic": "orders", "partition": 0, "replicas": [1,2,101]},
       {"topic": "orders", "partition": 1, "replicas": [2,3,101]}
     ]
   }
   EOF
   
   # 执行重分配(限流防止影响生产)
   kafka-reassign-partitions.sh \
     --bootstrap-server localhost:9092 \
     --reassignment-json-file reassign.json \
     --execute \
     --throttle 50000000  # 限制 50MB/s

4. 验证迁移进度
   kafka-reassign-partitions.sh \
     --bootstrap-server localhost:9092 \
     --reassignment-json-file reassign.json \
     --verify

5. 移除限流
   kafka-reassign-partitions.sh \
     --bootstrap-server localhost:9092 \
     --reassignment-json-file reassign.json \
     --verify  # 确认完成后自动移除限流

缩容(下线旧 Broker):

1. 将要下线的 Broker 上所有分区的 Leader 移走
   kafka-leader-election.sh --bootstrap-server localhost:9092 \
     --election-type preferred --topic <topic>

2. 将副本迁移到其他 Broker(同上 reassign)
   - 从 json 中移除下线 Broker 的 ID
   - 执行重分配

3. 确认该 Broker 不再承载任何 Leader 和 Follower
   kafka-metadata-quorum --bootstrap-server localhost:9092 describe --status

4. 安全停止 Broker
   kafka-server-stop.sh

5. 从 Controller 配置中移除该节点(KRaft 模式)
   更新 controller.quorum.voters,滚动重启剩余 Controller

5.6 KRaft 模式下的多数据中心复制

虽然 KRaft 解决了单集群的元管理问题,但**跨数据中心(Multi-Region)**复制仍需借助 MirrorMaker 2.0(官方推荐的集群间复制工具)。

MirrorMaker 2.0 双向复制架构:

Region A (主)                              Region B (备)
┌──────────────┐                         ┌──────────────┐
│ Kafka Cluster│  ─── MirrorSource ───→ │ Kafka Cluster│
│ (源)         │                         │ (目标)       │
│              │ ←── MirrorSource ────   │              │
└──────────────┘                         └──────────────┘

MirrorMaker 2.0 连接器:
  - MirrorSourceConnector:从源集群消费,写入目标集群
  - MirrorSinkConnector:可选,将目标集群数据写回源集群
  - MirrorCheckpointConnector:同步消费者偏移量
  - MirrorHeartbeatConnector:监控复制延迟

MirrorMaker 2.0 基于 Kafka Connect 框架,支持:

  • 精确一次(Exactly-Once)语义复制
  • 消费者偏移量同步(故障切换后消费者可从上次位置继续)
  • 主题配置和 ACL 自动同步
  • 延迟监控(Heartbeat 连接器测量 RTT)

6. 总结与选型建议

6.1 关键配置检查清单

□ replication.factor >= 3
□ min.insync.replicas = 2
□ unclean.leader.election.enable = false
□ auto.leader.rebalance.enable = true(定期平衡 Leader)
□ log.flush.interval.messages = 10000(刷盘频率)
□ log.retention.hours 按业务设置(磁盘容量允许)
□ KRaft 模式下:controller.quorum.voters 正确配置
□ 跨 AZ 部署,网络延迟 < 5ms
□ 监控 ISR 收缩告警(ISR 缩小 = 风险信号)

KRaft vs ZooKeeper 模式生产选型建议(2026):

维度ZooKeeper 模式KRaft 模式
成熟度非常成熟(十年生产验证)成熟(Kafka 3.3+ GA,4.0 将成为唯一模式)
运维复杂度高(需维护 ZK)低(单一系统)
扩展上限~20万 Partition数百万 Partition
故障恢复慢(ZK 超时 18s)快(Raft 心跳 < 500ms)
元数据延迟~10ms< 1ms
推荐场景已有 ZK 稳定运行、短期不迁移新建集群、大规模 Topic、低延迟要求
未来支持Kafka 4.0 弃用长期唯一支持

新集群投入生产的首选方案:

  • 选择 Kafka 3.6+ 且使用 KRaft 模式
  • 分离 Broker 和 Controller 角色
  • 3 节点 Quorum(跨机架/可用区)
  • 副本因子 3 + min.insync.replicas 2
  • 开启幂等性 Producer + acks=all
  • 部署 Rack Awareness 和副本分布均衡

对于仍在使用 ZooKeeper 的存量集群:

  • 制定 Kafka 3.6+ 升级加 KRaft 迁移计划
  • 迁移过程中使用双写保证业务连续性
  • 迁移完成后下线 ZooKeeper 释放运维资源

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「kafka」更多文章

  1. 集群升级与滚动重启实践
  2. Kafka 应用测试策略:Testcontainers 与集成测试
  3. 压缩算法选型:lz4、zstd、snappy 与 gzip