Skip to content
集群与高可用

集群与高可用

KRaft 时代的 Kafka 集群长什么样:Controller 怎么选、分区副本怎么同步(ISR/HW/LEO)、Leader 挂了会发生什么、扩缩容和滚动升级怎么操作。08 章的可靠性参数(min.insync.replicas)在这里能看到它们的机制来源。

前置阅读:00-安装与配置(KRaft 启动)、08-可靠性与幂等消费。


KRaft 架构:控制面与数据面

Kafka 4.x 只有 KRaft 模式(ZooKeeper 支持在 4.0 中彻底移除,迁移必须先在 3.x 完成)。集群分两种角色:

           ┌───────────── Controller Quorum(Raft 组,3 或 5 台)─────────────┐
           │   controller 0        controller 1        controller 2          │
           │        ▲ 选出一个 Active Controller,负责:                        │
           │        │  分区 Leader 选举 / 副本分配 / 元数据变更(全集群单点写入)  │
           └────────┼──────────────────────────────────────────────────────-─┘
                    │ 元数据事件流(__cluster_metadata,本质也是 Raft 日志)
        ┌───────────┴────────────┬──────────────────┐
   Broker 3 (node.id=3)     Broker 4           Broker 5
   ┌──────────────┐        ┌──────────────┐   ┌──────────────┐
   │ order-events-0│◀─复制──│ ...          │   │ ...          │
   │ (Leader)    │        │ (Follower)  │   │ (Follower)  │
   └──────────────┘        └──────────────┘   └──────────────┘
  • Broker:数据面。存分区日志、处理生产/请求,彼此通过 Follower 拉取复制。
  • Controller:控制面。维护集群元数据(Topic/分区/副本/成员),不再有 Controller Broker 一说之后元数据也不写外部系统——一切都在 quorum 的 Raft 日志里。
  • 部署形态:process.roles=broker,controller(混合,小规模/测试方便)或分离(生产推荐:Controller 3 台轻量节点、Broker 按容量横向加)。

控制器组:静态 vs 动态

老式静态配置每个节点都要写全所有投票者(00 章 format 用的就是它):

controller.quorum.voters=0@host0:9093,1@host1:9093,2@host2:9093

Kafka 3.9+ 引入动态 quorum(controller.quorum.bootstrap.servers),改控制器列表不再需要重启全集群,一次只能增减一个:

controller.quorum.bootstrap.servers=host0:9093,host1:9093,host2:9093

查看 quorum 健康度(Controller 的日志复制是否跟得上、谁是 Active):

.\bin\windows\kafka-metadata-quorum.bat --bootstrap-server localhost:9092 describe --status
# 关注:ClusterID、LeaderId and Epoch、HighWatermark、LastStableOffsetLag(各 voter 落后量)

注意:Controller quorum 同样遵循 Raft 多数派——3 台挂 2 台,集群元数据操作(建 Topic、Leader 选举、成员变更)全部停摆,但已有分区的读写不受影响(数据面自治)。这是 KRaft 相比 ZK 时代的重要进步:元数据故障不波及数据通路。


副本同步:LEO、HW、ISR

一个 3 副本分区的内部账本,每个副本记两个数:

                    offset:  0    1    2    3    4    5
Leader   (LEO=6)  ████████████████████████████|   ← 已追加到 5,LEO=6
Follower A(LEO=5)  ███████████████████████|        ← 落后一条
Follower B(LEO=3)  ███████████████|                ← 落后三条(网络抖了)

HW(High Watermark)=3 ← 三条里最慢的"在 ISR 内的"副本的 LEO 最小值
                      消费者只能读到 offset < HW 的消息
  • LEO(Log End Offset):该副本下一条要写的位置(02 章 kafka-get-offsets --time -1 读的就是 Leader 的 LEO)。
  • HW:ISR 集合内所有副本 LEO 的最小值。生产者 acks=all 等的是"ISR 全部落盘",消费者读到 HW 为止——HW 之前的消息保证所有 ISR 副本都有,Leader 挂了也不丢。
  • LSO(Last Stable Offset):事务版 HW,read_committed 的消费者只读 LSO 之前的已提交数据(08 章事务的读取端)。

ISR:会伸缩的同步名单

ISR(In-Sync Replicas) = 跟得上 Leader 的 Follower 名单(含 Leader 自己)。判定标准是 replica.lag.time.max.ms(默认 30s)内是否追平——慢副本会被踢出(ISR shrink),追回来再进(expand)。

把机制串起来看 08 章那三条配置的因果:

ISR 收缩到 < min.insync.replicas
   → acks=all 的 Produce 收到 NotEnoughReplicas 被拒(写保护,宁停不丢)
   → 若此时 Leader 崩溃且 ISR 只剩它自己:
        unclean.leader.election.enable=false → 分区离线,等原 Leader 回来(可用换持久)
        =true → 落后副本上位,HW 之后/未同步的数据消失(脏选举丢数据)

注意:UnderReplicatedPartitions 指标(0 是健康值)持续非零 = 有副本在掉队,先查磁盘 IO/网络/GC,而不是急着改 replica.lag.time.max.ms。

Leader 选举与首选 Leader

分区 Leader 只在 ISR 内产生(Controller 主导选举)。副本倾斜的常见形态:Broker 重启后 Leader 全挤在少数机器上(旧 Leader 回来不会自动抢回),吞吐随之倾斜。处理:

# 开启自动首选 Leader 再平衡(默认开):broker 端 auto.leader.rebalance.enable=true
# 手动触发一次(Preferred 即副本列表第一位,AR 顺序在建 Topic/重分配时决定):
.\bin\windows\kafka-leader-election.bat --bootstrap-server localhost:9092 \
  --election-type preferred --topic order-events --partition 0

更新的能力:ELR(Elastic/Eligible Leader Replicas,KIP-903) 允许从"同步过但已被踢出 ISR"的副本里选主,在整份 ISR 全灭的极端场景少丢数据、少停机——方向是"用一点严格性换可用性",仍是演进中的特性(4.x 云厂商如 MSK 已商用),开启前先读 release note 再决定。


集群操作实战

搭建要点(3 Broker + 3 Controller 混合最小集群,生产分离部署同理)

每个节点的 server.properties 关键差异只有三样(以 node 0 为例):

process.roles=broker,controller
node.id=0                      # 全集群唯一
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
controller.quorum.voters=0@n0:9093,1@n1:9093,2@n2:9093   # 或动态 quorum 写法
log.dirs=/data/kafka-logs

其余参数(num.partitions、default.replication.factor=3、min.insync.replicas=2、unclean.leader.election.enable=false、offsets.topic.replication.factor=3)集群统一,落盘前对照 08 检查清单。每节点执行一次 kafka-storage format -t <uuid> -c server.properties(多节点集群不带 --standalone)。

加 Broker 与数据再平衡

新节点 node.id=3 起进程后不会自动接盘——分区分配是静态账本,要手动触发再平衡:

# 1. 生成方案:把 order-events 的副本挪到新机(throttle 限流,避免搬迁挤垮在线读写)
.\bin\windows\kafka-reassign-partitions.bat --bootstrap-server localhost:9092 \
  --topics-to-move-json-file topics.json --broker-list "0,1,2,3" --generate \
  > reassign.json

# 2. 执行方案(reassign.json 里可手工加 throttle 字段)
.\bin\windows\kafka-reassign-partitions.bat --bootstrap-server localhost:9092 \
  --reassignment-json-file reassign.json --execute

# 3. 验证完成 + 解除限流
.\bin\windows\kafka-reassign-partitions.bat --bootstrap-server localhost:9092 \
  --reassignment-json-file reassign.json --verify

下线 Broker(Decommission)

不能直接拔机器——先把该节点上的副本全部迁走(同上三步,--broker-list 不包含待下线节点),Lag 清零、UnderReplicatedPartitions=0 后再停进程。动态 quorum 下摘 Controller 用 kafka-metadata-quorum.bat/admin API 的 remove 流程,一次一个,先摘 follower 最后动 leader(或先 resign)。

滚动升级顺序(KRaft)

先 Controller 后 Broker,且版本跨度要按官方矩阵逐级(4.x 内小版本直接滚):

  1. --describe 确认当前 metadata.version;
  2. 逐个升级 Controller(一次一个,等 quorum 恢复);
  3. 逐个滚动 Broker(每停一台,确认其上没有唯一 Leader 分区/ISR 已恢复再停下一台);
  4. 全部 Broker 版本就绪后,kafka-features.bat --bootstrap-server ... --upgrade --feature <name> 提升特性版本(不再兼容老客户端时才做)。

机架感知

云集群跨可用区部署时给 Broker 打上 broker.rack=az-a,并开 replica.selector.class=org.apache.kafka.common.replica.RackAwareReplicaSelector——消费者优先读同 AZ 副本,省跨区流量费和延迟。副本分配也会尽量铺在不同 rack 上。


多集群与容灾:MirrorMaker 2

MM2(Kafka Connect 内置的跨集群复制器)是官方标准方案:Topic 数据跨集群/跨地域复制,并翻译消费 Offset(消费者切集群不重复消费)。典型姿势:A 地域集群 → 双向/单向同步到 B 地域;灾备切换时把客户端 bootstrap 指向对端。

# connector 配置 mm2.properties(要点)
clusters = primary, backup
primary.bootstrap.servers = pk-broker:9092
backup.bootstrap.servers = bk-broker:9092
primary->backup.topics = order-.*          # 正则圈范围
topics.blacklist = .*\.consumer_offsets.*  # 内部 Topic 不同步
replication.factor.alter = 3               # 对端副本数不足时改写
sync.group.offsets.enabled = true          # Offset 翻译:灾备切换续消费的关键

注意:MM2 的复制是异步的——切换 RPO 不为零。切换演练时按"停写→等 Lag 归零→改指→恢复写"的顺序走,顺序错了就是数据分叉。MM1 已在 4.0 移除,老教程里的命令作废。


Kafka 4.x 演进速览(2025-2026)

  • 4.0(2025-03):ZooKeeper 正式下线;Broker 要求 Java 17;清理了大批 2.x 前的旧协议。
  • 4.1:Controller 迁移/切换更平滑(KIP-890),生产者侧限流(KIP-714,多租户集群控量)。
  • 4.2(2026-02):Kafka Queues(Share Groups)GA——组内逐条 ACK + RENEW 续期,队列语义补齐(10 章末尾提过);Streams 原生 DLQ。
  • 4.3(2026-05):Broker 隔离/引流(cordoning,维护节点先摘流量再下线)、Streams Header 保持、分区级容量指标;4.3.1 修了 Streams RocksDB 本地内存泄漏。
  • 分层存储持续完善(11 章),动态 Controller Quorum 成为新集群默认姿势。

趋势一句话:Kafka 正在从"流日志"扩成"流 + 队列"平台,但高可用的底座没变——副本数、ISR、不脏选举,把这三样守住,比追任何新特性都重要。


监控要看的指标

Broker 端(JMX 或 Prometheus 的 kafka_exporter,参见 prometheus 手册):

指标 健康线 异常含义
UnderReplicatedPartitions 0 有副本掉线/追不上,数据冗余度在裸奔
ActiveControllerCount(全集群求和) 1 0=quorum 丢了(控制面瘫痪);>1=脑裂迹象
IsrShrinksPerSec ≈0 ISR 频繁收缩,查磁盘/网络/GC
NetworkProcessorAvgIdlePercent >30% 网络线程饱和,加 num.network.threads 或加机器
LogFlushRateAndTimeMs / 磁盘 util 持续高=危险 fsync 排队,写放大或盘不够
消费 Lag(02 章 consumer-groups) 业务自定义 追不上是容量问题,突然跳增是事故现场

实战建议

  1. 生产集群 Controller 与 Broker 分离,3 台 Controller 起步;混合部署只属于单机玩具和小规模内部集群。
  2. 副本 3 / ISR 2 是绝大多数业务的地板:default.replication.factor=3、min.insync.replicas=2 直接写进集群默认,别指望建 Topic 的人记得。
  3. 任何"先停一台 Broker 看看"的操作,先看那台上有没有唯一 ISR Leader——kafka-topics --describe --under-replicated-partitions 是停机前的保命命令。
  4. 升级、扩容、再平衡统一遵循"限流、逐个、验证"三步法;搬迁限流(throttle)忘了解除会拖慢后续一切再平衡。
  5. 容灾方案落地只有两条硬道理:RPO 能不能接受(MM2 异步必非零)、Offset 会不会翻译(sync.group.offsets.enabled)。演练时两条都要实测,纸面推演不算数。

参考资料