1. 背景
随着 Kafka 集群规模不断扩大,存储成本与性能成为瓶颈问题。传统 Kafka 依赖本地磁盘存储所有消息日志(log segment),随着 Topic 数量与保留时间增加,磁盘 I/O 压力和扩容成本快速上升。
为解决此问题,Kafka 在 3.x 版本引入了 Tiered Storage(分层存储) 特性:将历史冷数据迁移到远程存储(如对象存储 S3、HDFS),本地仅保留热数据,降低磁盘压力的同时支持更长时间的保留策略。
2. Kafka 存储分层架构
Kafka 的存储主要分为两层:
存储层级 |
特点 |
典型存储介质 |
---|---|---|
Local Storage |
热数据,频繁读写,延迟敏感 |
SSD / NVMe 本地磁盘 |
Remote Storage |
冷数据,历史数据访问频率低 |
对象存储(S3、HDFS) |
整体数据流动示意:
Producer -> Kafka Broker (本地磁盘刷盘) ->
历史 Segment 异步上传至 Remote Storage
2.1 分层触发机制
Kafka 基于 Segment 文件滚动机制 触发分层:
-
当当前 log segment 大小超过配置阈值(log.segment.bytes)或 时间超过阈值(log.roll.ms)时滚动。
-
旧 Segment 被标记为 可上传状态。
-
后台线程异步将这些旧 Segment 上传至远程存储,完成后标记为 RemoteSegment。
// org.apache.kafka.server.log.remote.storage.LocalLogSegmentMetadata
if (segment.isClosed() && !segment.isUploaded()) {
remoteLogManager.upload(segment);
}
3. Kafka 本地刷盘逻辑
Kafka 写入过程的核心是 刷盘策略,确保数据可靠落盘,同时兼顾吞吐和延迟。
3.1 核心配置参数
参数 |
描述 |
默认值 |
---|---|---|
log.flush.interval.messages |
每写入多少条消息触发一次刷盘 |
Long.MAX |
log.flush.interval.ms |
每隔多少毫秒触发一次刷盘 |
Long.MAX |
flush.messages/flush.ms |
对 Topic 级别的刷盘控制 |
- |
acks |
Producer 端确认策略,影响刷盘时机 |
1 |
3.2 消息写入流程
Kafka 消息写入本地磁盘的流程:
Producer -> Broker Network Thread ->
LogAppend -> MappedByteBuffer (Page Cache) ->
条件触发刷盘 -> fsync()
核心代码在 LogSegment.append():
public void append(ByteBufferMessageSet messages) {
// 将消息写入 FileChannel 或 MappedByteBuffer
channel.write(messages.getBuffer());
// 刷盘条件判断
if (shouldFlush()) {
flush();
}
}
3.3 刷盘实现
flush() 通过 FileChannel.force() 或 MappedByteBuffer.force() 将 Page Cache 数据落盘:
public void flush() {
try {
fileChannel.force(true); // 强制刷盘,true 表示同时刷 metadata
lastFlushTime.set(SystemTime.milliseconds());
} catch (IOException e) {
log.error("Flush failed", e);
}
}
4. 分层存储与刷盘交互
分层存储引入后,Kafka 需要协调 本地刷盘 和 远程上传:
-
刷盘优先:Segment 必须完全刷盘后,才允许上传至远程存储,避免未落盘数据丢失。
-
异步上传:RemoteLogManager 通过后台线程定期扫描已关闭的 Segment 并执行上传。
-
本地清理:当远程上传完成,并且本地保留策略到期后,才能删除本地副本,释放磁盘空间。
关键源码在 RemoteLogManager:
if (segment.isClosed() && segment.isFlushed()) {
remoteStorage.upload(segment);
}
5. Segment 生命周期
以一个 Segment 为例,其完整生命周期如下:
[ACTIVE] -> 滚动关闭 -> [FLUSHED] -> 上传远程存储 -> [REMOTE] -> 本地删除
对应的关键状态流转:
阶段 |
操作 |
触发条件 |
---|---|---|
ACTIVE |
正在写入 |
当前活跃 Segment |
FLUSHED |
刷盘完成 |
flush() 调用 |
REMOTE |
上传至远程存储完成 |
RemoteLogManager |
DELETED |
本地文件被清理 |
Retention Policy |
6. 性能与可靠性权衡
设计目标 |
策略 |
影响 |
---|---|---|
高吞吐 |
依赖 Page Cache,异步刷盘 |
低延迟,但断电风险 |
高可靠性 |
强制刷盘,Producer acks=-1 |
延迟增加 |
低成本 |
分层存储,热冷分离 |
降低本地磁盘占用 |
数据可用性 |
上传前必须完成刷盘和副本同步 |
确保远程存储一致性 |
7. 总结
Kafka 的分层存储通过 热冷分离 显著降低了本地磁盘压力,使得集群能够以低成本保存大量历史数据。同时,Kafka 依旧保留了灵活的刷盘机制,通过参数调整在 性能 与 可靠性 之间找到平衡点。
源码分析表明,分层存储在设计上严格依赖刷盘完成后才进行远程上传,从而确保远程存储的副本数据一致性。这种设计既保证了数据安全,又为未来的扩展和多云部署奠定了基础。