集群与高可用
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:9093Kafka 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 内小版本直接滚):
--describe确认当前metadata.version;- 逐个升级 Controller(一次一个,等 quorum 恢复);
- 逐个滚动 Broker(每停一台,确认其上没有唯一 Leader 分区/ISR 已恢复再停下一台);
- 全部 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) | 业务自定义 | 追不上是容量问题,突然跳增是事故现场 |
实战建议
- 生产集群 Controller 与 Broker 分离,3 台 Controller 起步;混合部署只属于单机玩具和小规模内部集群。
- 副本 3 / ISR 2 是绝大多数业务的地板:
default.replication.factor=3、min.insync.replicas=2直接写进集群默认,别指望建 Topic 的人记得。 - 任何"先停一台 Broker 看看"的操作,先看那台上有没有唯一 ISR Leader——
kafka-topics --describe --under-replicated-partitions是停机前的保命命令。 - 升级、扩容、再平衡统一遵循"限流、逐个、验证"三步法;搬迁限流(throttle)忘了解除会拖慢后续一切再平衡。
- 容灾方案落地只有两条硬道理:RPO 能不能接受(MM2 异步必非零)、Offset 会不会翻译(
sync.group.offsets.enabled)。演练时两条都要实测,纸面推演不算数。