Kafka KRaft模式详解:告别ZooKeeper的新时代
引言
2021年,Apache Kafka发布了KIP-500提案的实现——KRaft(Kafka Raft)模式,这标志着Kafka正式开始摆脱对ZooKeeper的依赖。经过多年的发展,Kafka 3.3版本已将KRaft标记为生产就绪(Production Ready)。
为什么要移除ZooKeeper?使用了十多年的架构为何要改变?KRaft带来了什么优势?本文将深入探讨Kafka KRaft模式的方方面面。
为什么需要KRaft
ZooKeeper时代的痛点
具体问题分析
| 问题领域 | ZooKeeper模式 | 具体影响 |
|---|---|---|
| 运维成本 | 需要独立部署ZK集群 | 额外的硬件、监控、维护成本 |
| 性能瓶颈 | 所有元数据变更经过ZK | 大规模集群下成为瓶颈 |
| 恢复时间 | Controller故障转移慢 | 可能需要几分钟,期间集群不可用 |
| 扩展限制 | ZK的Znode大小限制 | 限制了分区和主题数量 |
| 网络分区 | ZK和Kafka可能不一致 | 增加了故障诊断难度 |
️ KRaft架构概述
核心架构对比
KRaft的设计理念
核心思想:
- 内置共识机制:基于Raft算法
- 元数据即日志:Event-Sourcing模式
- 角色分离:Controller和Broker可分离部署
- 简化架构:移除外部依赖
关键组件:
Controller Quorum:
- 管理集群元数据
- 基于Raft的共识
- 事件日志存储
Metadata Log:
- 不可变的事件流
- 所有元数据变更的记录
- 支持快照和压缩
Broker:
- 订阅元数据变更
- 缓存元数据快照
- 处理客户端请求
KRaft工作原理
元数据管理机制
元数据日志(Metadata Log)
// KRaft元数据记录类型示例
public sealed interface MetadataRecord {
// 主题相关
record TopicRecord(
String topicName,
Uuid topicId,
int partitionCount,
short replicationFactor,
Map<String, String> configs
) implements MetadataRecord {}
// 分区相关
record PartitionRecord(
int partitionId,
Uuid topicId,
List<Integer> replicas,
List<Integer> isr,
Integer leader,
int leaderEpoch
) implements MetadataRecord {}
// Broker相关
record BrokerRegistrationRecord(
int brokerId,
long brokerEpoch,
String host,
int port,
String rack,
Features features
) implements MetadataRecord {}
// 配置相关
record ConfigRecord(
String resourceType,
String resourceName,
String configName,
String configValue
) implements MetadataRecord {}
}
// 元数据日志处理
public class MetadataLogManager {
private final Log<MetadataRecord> log;
private final SnapshotRegistry snapshots;
private volatile MetadataImage currentImage;
public void appendRecord(MetadataRecord record) {
// 1. 写入Raft日志
long offset = log.append(record);
// 2. 等待复制到多数派
awaitHighWatermark(offset);
// 3. 应用到状态机
currentImage = currentImage.apply(record);
// 4. 通知订阅者
notifyListeners(record);
}
// 快照机制
public void createSnapshot() {
if (shouldCreateSnapshot()) {
MetadataSnapshot snapshot = currentImage.toSnapshot();
snapshots.create(snapshot, log.latestOffset());
// 清理旧日志
log.truncateTo(snapshot.offset());
}
}
}
Controller Quorum
Controller选举过程
Quorum配置示例
# KRaft模式配置 - Controller节点
process.roles=controller,broker # 节点角色:可以是controller、broker或both
node.id=1 # 节点ID,必须唯一
controller.quorum.voters=1@controller1:9093,2@controller2:9093,3@controller3:9093
# Controller专用配置
controller.listener.names=CONTROLLER
controller.listener.security.protocol.map=CONTROLLER:PLAINTEXT
listeners=CONTROLLER://controller1:9093
# 元数据日志配置
metadata.log.dir=/var/kafka-metadata
metadata.log.segment.bytes=1073741824 # 1GB
metadata.max.retention.bytes=10737418240 # 10GB
metadata.max.retention.ms=604800000 # 7天
# Raft相关配置
controller.quorum.election.timeout.ms=1000
controller.quorum.fetch.timeout.ms=2000
controller.quorum.election.backoff.max.ms=1000
KRaft vs ZooKeeper 性能对比
关键指标对比

性能测试数据
| 测试场景 | ZooKeeper模式 | KRaft模式 | 提升倍数 |
|---|---|---|---|
| Controller故障转移 | 30-120秒 | <1秒 | 30-120x |
| 创建1000个主题 | 15秒 | 2秒 | 7.5x |
| 10万分区元数据加载 | 45秒 | 5秒 | 9x |
| 支持最大分区数 | 20万 | 100万+ | 5x+ |
| 控制器内存使用 | 2GB | 500MB | 4x优化 |
迁移到KRaft
迁移策略
新集群部署KRaft
# 1. 生成集群ID
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
# 2. 格式化存储目录
bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID \
-c config/kraft/server.properties
# 3. 启动Controller节点
bin/kafka-server-start.sh config/kraft/controller.properties
# 4. 启动Broker节点
bin/kafka-server-start.sh config/kraft/broker.properties
# 5. 验证集群状态
bin/kafka-metadata.sh --snapshot /var/kafka-logs/__cluster_metadata-0/00000000000000000000.log
现有集群迁移(Kafka 3.5+)
# Phase 1: 准备阶段
# 1. 升级到支持迁移的版本
# 2. 配置dual-write模式
# Phase 2: 迁移元数据
bin/kafka-metadata-migration.sh \
--zookeeper localhost:2181 \
--kraft-controller-quorum 1@host1:9093,2@host2:9093,3@host3:9093 \
--perform-migration
# Phase 3: 切换到KRaft
# 修改配置,重启Broker
# Phase 4: 清理ZooKeeper
# 确认无误后,关闭ZooKeeper
KRaft特性详解
1️⃣ 事件源(Event Sourcing)模式
// KRaft使用事件源模式管理元数据
public class MetadataEventProcessor {
// 所有状态变更都是事件
public void processCreateTopic(CreateTopicRequest request) {
// 生成事件
TopicRecord topicRecord = new TopicRecord(
request.name(),
Uuid.randomUuid(),
request.partitions(),
request.replicationFactor(),
request.configs()
);
// 验证事件
validateTopicRecord(topicRecord);
// 追加到日志
long offset = metadataLog.append(topicRecord);
// 等待复制
awaitReplication(offset);
// 应用到状态
metadataCache.apply(topicRecord);
}
// 通过重放事件重建状态
public MetadataImage rebuildState() {
MetadataImage image = MetadataImage.EMPTY;
for (MetadataRecord record : metadataLog) {
image = image.apply(record);
}
return image;
}
}
2️⃣ 快照机制
public class SnapshotManager {
private static final long SNAPSHOT_INTERVAL_RECORDS = 1000000;
public void generateSnapshot() {
MetadataImage currentImage = getCurrentImage();
long snapshotOffset = metadataLog.highWatermark();
// 创建快照
try (SnapshotWriter writer = new SnapshotWriter(snapshotOffset)) {
// 写入所有主题
currentImage.topics().forEach(topic ->
writer.write(topic.toRecord())
);
// 写入所有分区
currentImage.partitions().forEach(partition ->
writer.write(partition.toRecord())
);
// 写入所有Broker
currentImage.brokers().forEach(broker ->
writer.write(broker.toRecord())
);
writer.freeze();
}
// 删除旧日志
metadataLog.deleteBeforeOffset(snapshotOffset);
}
}
3️⃣ 元数据缓存
public class BrokerMetadataCache {
private volatile MetadataImage image;
private final MetadataSnapshotter snapshotter;
// Broker订阅Controller的元数据更新
public void startMetadataListener() {
metadataListener = new MetadataListener() {
@Override
public void onMetadataUpdate(MetadataDelta delta) {
// 增量更新
MetadataImage newImage = image.apply(delta);
// 原子更新
image = newImage;
// 通知本地组件
notifyListeners(delta);
}
};
controller.subscribe(metadataListener);
}
// 快速查询
public Optional<TopicMetadata> getTopic(String topicName) {
return Optional.ofNullable(image.topics().get(topicName));
}
}
KRaft优势总结
架构优势

具体收益
| 方面 | 具体改进 | 业务价值 |
|---|---|---|
| 运维成本 | 减少50%组件数量 | 降低维护成本 |
| 恢复时间 | 从分钟级到秒级 | 提高可用性 |
| 集群规模 | 支持5倍更多分区 | 支撑业务增长 |
| 资源使用 | 减少30%内存使用 | 降低硬件成本 |
| 部署复杂度 | 简化部署流程 | 加快上线速度 |
⚠️ 注意事项与限制
当前限制(Kafka 3.7)
功能限制:
- JBOD(Just a Bunch Of Disks): 部分支持
- 分层存储: 实验性支持
- 某些Admin API: 迁移中
版本要求:
- 生产使用: Kafka 3.3+
- 完整功能: Kafka 3.5+
- 推荐版本: Kafka 3.7+
迁移考虑:
- 不可逆: KRaft转ZK不支持
- 停机时间: 可能需要短暂停机
- 配置变更: 需要更新客户端配置
最佳实践
部署建议:
Controller节点:
- 独立部署: 3或5个节点
- 硬件要求: SSD存储,稳定网络
- 不处理数据: 仅管理元数据
Broker节点:
- 可混合部署: controller+broker
- 分离部署更稳定: 大规模集群建议
监控要点:
- 元数据日志大小
- Controller选举次数
- 元数据同步延迟
- 快照生成频率
未来展望
KRaft路线图

发展方向
- 完全移除ZooKeeper:预计2026年完全移除
- 性能持续优化:支持千万级分区
- 云原生支持:更好的Kubernetes集成
- 多区域支持:跨区域元数据同步
- 自动化运维:智能化管理
实战示例
搭建3节点KRaft集群
# 节点1配置 (controller1.properties)
process.roles=controller,broker
node.id=1
controller.quorum.voters=1@localhost:19092,2@localhost:29092,3@localhost:39092
listeners=PLAINTEXT://localhost:19092,BROKER://localhost:19093
inter.broker.listener.name=BROKER
advertised.listeners=BROKER://localhost:19093
controller.listener.names=PLAINTEXT
log.dirs=/tmp/kraft-node-1
metadata.log.dir=/tmp/kraft-metadata-1
# 节点2配置 (controller2.properties)
process.roles=controller,broker
node.id=2
controller.quorum.voters=1@localhost:19092,2@localhost:29092,3@localhost:39092
listeners=PLAINTEXT://localhost:29092,BROKER://localhost:29093
inter.broker.listener.name=BROKER
advertised.listeners=BROKER://localhost:29093
controller.listener.names=PLAINTEXT
log.dirs=/tmp/kraft-node-2
metadata.log.dir=/tmp/kraft-metadata-2
# 节点3配置 (controller3.properties)
process.roles=controller,broker
node.id=3
controller.quorum.voters=1@localhost:19092,2@localhost:29092,3@localhost:39092
listeners=PLAINTEXT://localhost:39092,BROKER://localhost:39093
inter.broker.listener.name=BROKER
advertised.listeners=BROKER://localhost:39093
controller.listener.names=PLAINTEXT
log.dirs=/tmp/kraft-node-3
metadata.log.dir=/tmp/kraft-metadata-3
# 启动脚本
#!/bin/bash
# 生成集群ID
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
echo "Cluster ID: $KAFKA_CLUSTER_ID"
# 格式化存储
for i in 1 2 3; do
bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID \
-c config/kraft/controller${i}.properties
done
# 启动节点
for i in 1 2 3; do
bin/kafka-server-start.sh -daemon config/kraft/controller${i}.properties
done
# 验证集群
bin/kafka-broker-api-versions.sh --bootstrap-server localhost:19093
总结
KRaft模式是Kafka架构演进的重要里程碑,它通过以下创新彻底改变了Kafka的元数据管理:
- 架构简化:移除ZooKeeper依赖,降低运维复杂度
- 性能飞跃:秒级故障恢复,支持百万级分区
- 一致性增强:基于Raft的强一致性保证
- 扩展性提升:支持更大规模的集群部署
- 云原生友好:更适合容器化和云环境
一句话总结:KRaft不仅是技术升级,更是Kafka走向下一个十年的关键一步。它让Kafka变得更简单、更快速、更可靠,真正实现了"简单就是终极的复杂"这一设计理念。
对于新项目,强烈建议直接采用KRaft模式;对于现有项目,应该开始规划迁移路线,拥抱Kafka的新时代!
浙公网安备 33010602011771号