searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Kafka 分层存储与刷盘逻辑深度解析

2025-09-30 00:56:27
2
0

 

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 文件滚动机制 触发分层:

 

  1. 当当前 log segment 大小超过配置阈值log.segment.bytes)或 时间超过阈值log.roll.ms)时滚动。

  2. 旧 Segment 被标记为 可上传状态

  3. 后台线程异步将这些旧 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 需要协调 本地刷盘远程上传

 

  1. 刷盘优先:Segment 必须完全刷盘后,才允许上传至远程存储,避免未落盘数据丢失。

  2. 异步上传:RemoteLogManager 通过后台线程定期扫描已关闭的 Segment 并执行上传。

  3. 本地清理:当远程上传完成,并且本地保留策略到期后,才能删除本地副本,释放磁盘空间。

 

关键源码在 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 依旧保留了灵活的刷盘机制,通过参数调整在 性能可靠性 之间找到平衡点。

源码分析表明,分层存储在设计上严格依赖刷盘完成后才进行远程上传,从而确保远程存储的副本数据一致性。这种设计既保证了数据安全,又为未来的扩展和多云部署奠定了基础。

0条评论
作者已关闭评论
l****n
3文章数
0粉丝数
l****n
3 文章 | 0 粉丝
l****n
3文章数
0粉丝数
l****n
3 文章 | 0 粉丝
原创

Kafka 分层存储与刷盘逻辑深度解析

2025-09-30 00:56:27
2
0

 

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 文件滚动机制 触发分层:

 

  1. 当当前 log segment 大小超过配置阈值log.segment.bytes)或 时间超过阈值log.roll.ms)时滚动。

  2. 旧 Segment 被标记为 可上传状态

  3. 后台线程异步将这些旧 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 需要协调 本地刷盘远程上传

 

  1. 刷盘优先:Segment 必须完全刷盘后,才允许上传至远程存储,避免未落盘数据丢失。

  2. 异步上传:RemoteLogManager 通过后台线程定期扫描已关闭的 Segment 并执行上传。

  3. 本地清理:当远程上传完成,并且本地保留策略到期后,才能删除本地副本,释放磁盘空间。

 

关键源码在 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 依旧保留了灵活的刷盘机制,通过参数调整在 性能可靠性 之间找到平衡点。

源码分析表明,分层存储在设计上严格依赖刷盘完成后才进行远程上传,从而确保远程存储的副本数据一致性。这种设计既保证了数据安全,又为未来的扩展和多云部署奠定了基础。

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
1
0