
Kafka 的数据要留得久,broker 磁盘就跟着膨胀。三个月的订单事件、一年的埋点日志,三副本全在 broker 本地盘上,扩容节奏被 retention 绑死。KIP-405 分层存储就是为了解开这个绑定:滚过的 Segment 搬到对象存储,本地只留热尾巴。这个功能从 3.6 的 early access 走到 3.9.0 才生产可用,Apache Kafka 本身不带存储后端实现,落地要自己装一个 RemoteStorageManager 插件。这篇用 Aiven 开源的 RSM 插件,把远程层接到自建的 RustFS(S3 兼容)上,从部署到验证走一遍。
先把概念分清
两套 retention。 开了分层后每个 topic 有两个保留窗口:retention.ms / retention.bytes 管总保留量(含远端),local.retention.ms / local.retention.bytes 管本地留多久。本地保留可以缩到几小时,总保留拉到几个月。有个容易踩的点:local retention 不显式设置时会回落到总 retention 的值------数据照样会下沉(上传由 Segment 滚动触发,跟这个参数无关),但本地副本不会提前删,磁盘该涨还是涨,分层的意义直接减半,所以这两个参数必须显式配。
只搬滚过的 Segment。 active segment 永远在 broker 本地,只有滚动关闭的 Segment 会被复制到远端,且本地副本删除一定发生在上传成功之后。对生产者和消费者完全透明,客户端不用改代码,读已经离开本地盘的 offset 时,broker 从远端取回来返回。
两个插件接口加一个内部 topic。 数据面是 RemoteStorageManager(复制、取回、删除 Segment),控制面是 RemoteLogMetadataManager(记录哪个 Segment 在哪)。Kafka 自带默认的元数据管理器,把元数据存在内部 topic __remote_log_metadata 里。但存储后端 Kafka 一个都不带,源码里的 LocalTieredStorage 只用于集成测试。社区里最常见的开源实现是 Aiven 的 tiered-storage-for-apache-kafka,支持 S3、GCS、Azure Blob,开源且维护活跃。
版本门槛:3.6.0 early access,3.9.0 生产可用。KIP-950(按 topic 关闭分层)、KIP-956(上传/下载配额)、KIP-1005(远程 offset 暴露给客户端)也都在 3.9 落地。admin 工具要求客户端 3.0 以上。
第一步:起一个 RustFS 当远程层
任何 S3 兼容对象存储都能当远程层,自托管场景用 RustFS 就一行 docker 命令的事。按官方文档:
bash
docker pull rustfs/rustfs:latest
docker volume create rustfs-data
docker run -d \
--name rustfs \
--restart unless-stopped \
-p 9000:9000 \
-p 9001:9001 \
-v rustfs-data:/data \
-e RUSTFS_ACCESS_KEY="<your-access-key>" \
-e RUSTFS_SECRET_KEY="<your-secret-key>" \
-e RUSTFS_ADDRESS=":9000" \
-e RUSTFS_CONSOLE_ADDRESS=":9001" \
-e RUSTFS_CONSOLE_ENABLE=true \
rustfs/rustfs:latest \
/data
验证健康检查,然后用 AWS CLI 建一个专用的桶:
bash
curl --fail http://localhost:9000/health
export AWS_ACCESS_KEY_ID=<your-access-key>
export AWS_SECRET_ACCESS_KEY=<your-secret-key>
aws --endpoint-url http://localhost:9000 s3 mb s3://kafka-tiered
有一个设计上的事提前说清楚:这个桶里的对象由 Kafka 自己管理,Segment 过期时 broker 会主动删对象。所以不要在这个桶上再叠一层 lifecycle expiration 规则,两套删除逻辑叠加,轻则多余请求,重则元数据还指向对象、对象已经被外部规则删掉。
第二步:装 Aiven RSM 插件
从 tiered-storage-for-apache-kafka 的 GitHub Releases 下载两个包:core-<version>.tgz 和 s3-<version>.tgz(版本号以 releases 页当前为准),解压到 Kafka broker 的插件目录,结构大致是:
/opt/kafka/plugins/aiven-tiered-storage/
├── core/ # core 包的 jar
└── s3/ # s3 包的 jar(S3StorageBackend 及 AWS SDK 依赖)
第三步:配置 broker
server.properties 里加这些(多 broker 滚动重启生效):
properties
remote.log.storage.system.enable=true
remote.log.storage.manager.class.name=io.aiven.kafka.tieredstorage.RemoteStorageManager
remote.log.storage.manager.class.path=/opt/kafka/plugins/aiven-tiered-storage/core/*:/opt/kafka/plugins/aiven-tiered-storage/s3/*
remote.log.storage.manager.impl.prefix=rsm.config.
remote.log.metadata.manager.class.name=org.apache.kafka.server.log.remote.metadata.storage.TopicBasedRemoteLogMetadataManager
remote.log.metadata.manager.listener.name=PLAINTEXT
rlmm.config.remote.log.metadata.topic.replication.factor=1
rlmm.config.remote.log.metadata.topic.min.isr=1
rsm.config.storage.backend.class=io.aiven.kafka.tieredstorage.storage.s3.S3StorageBackend
rsm.config.storage.s3.bucket.name=kafka-tiered
rsm.config.storage.s3.region=us-east-1
rsm.config.storage.s3.endpoint.url=http://<rustfs-host>:9000
rsm.config.storage.s3.path.style.access.enabled=true
rsm.config.aws.access.key.id=<your-access-key>
rsm.config.aws.secret.access.key=<your-secret-key>
rsm.config.chunk.size=4194304
几个 key 单独说一下:
s3.endpoint.url:官方文档写明这个 key 就是为 S3 兼容后端准备的(文档举例是 SeaweedFS,S3 兼容的 RustFS 同理),不设则连 AWS S3。s3.path.style.access.enabled=true:自托管对象存储显式开成 true 最稳。留空时由 SDK 自动探测,Amazon S3 默认走 virtual-host 风格(bucket 当子域名),IP 或内部域名的自建服务解析不了这种地址。chunk.size:Segment 会被切块存储以支持按范围取回,官方建议从 4 MiB 起步做基准测试。storage.s3.multipart.upload.part.size默认 25 MiB,一般不用动。- 如果升级后遇到校验类报错,有个兼容开关
rsm.config.storage.s3.legacy.md5.plugin.enabled,是为依赖旧 MD5 校验行为的老 S3 兼容存储准备的。
第四步:给 topic 打开分层
bash
bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic order-events \
--config remote.storage.enable=true \
--config segment.bytes=536870912 \
--config local.retention.ms=86400000 \
--config retention.ms=2592000000
这组配置的含义:本地留 1 天,总量留 30 天,剩下 29 天的数据在对象存储里。segment.bytes 调小能让下沉更及时(Segment 滚动才上传),代价是对象数量和请求数变多。已有 topic 用 kafka-configs.sh --alter 加同样的 config 即可。
验证
按顺序验证:
bash
bin/kafka-topics.sh --bootstrap-server localhost:9092 --list | grep remote
aws --endpoint-url http://localhost:9000 s3 ls s3://kafka-tiered --recursive
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic order-events --offset <old-offset> --partition 0
监控上看 RemoteLogManager 的 JMX 指标(上传滞后、复制耗时、错误计数),RLM 落后意味着该下沉的数据堆在本地盘,分层就白开了。
退出路径(KIP-950,仅 KRaft)
3.9 之前分层开了就关不掉,KIP-950 给了两条退路:
bash
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --entity-type topics --entity-name order-events \
--add-config 'remote.log.copy.disable=true,local.retention.ms=-2,local.retention.bytes=-2'
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --entity-type topics --entity-name order-events \
--add-config 'remote.storage.enable=false,remote.log.delete.on.disable=true'
-2 的作用是把本地保留对齐到总保留。另外有个单向门:broker 级关掉 remote.log.storage.system.enable 之前,必须先删光所有分层 topic,否则 broker 起不来。
限制清单(官方文档列的,别撞上)
Kafka 官方 tiered storage 运维文档把这几条列得很直白:
- 不支持 compacted topics,开了分层的 topic 也不能再把 cleanup.policy 改成 compact。K-V 状态回放类的压缩主题走不了这条路。
- Kafka 2.8 之前创建的 topic 可能缺 producer snapshot 文件,这类老 Segment 不被支持。
- broker 级关闭要先删光分层 topic(上面说过)。
- 读远端数据依赖存活的 broker,
remote.fetch.max.wait.ms默认 500ms,远端读慢的场景可能要调大。
还有一条要心里有数的:分层存储不是备份。远端 Segment 仍是活日志的一部分,topic 删除会连着删远端数据,误写入的坏数据远端一样有,跨集群恢复没有机制。它解决的是保留成本,不是可恢复性,后者要独立的备份方案。
什么场景值得上
对照着判断:
- retention 拉到月级、年级,broker 磁盘成本是痛点 → 值得
- 历史数据读得少(偶尔回填、审计),尾读为主 → 值得,对象存储的成本结构正好匹配这种访问模式
- topic 是 delete 策略的日志流 → 适合
- compacted topic、频繁全量回放、retention 只有几天 → 本地盘更划算,别折腾
分层存储把"留多久"和"买多少盘"解耦了,自托管侧加一个 S3 兼容的 RustFS 就能把这套架构跑起来,插件和元数据 topic 的运维成本是主要代价。retention 长的集群,这笔账不难算。