AWS 新发布的 S3 Files 适合作为 Kafka 的存储吗?

0 阅读10分钟

cover.webp

Kafka 社区对共享存储的兴趣由来已久:如果所有数据都放在 S3 这样的共享存储上,Broker 就不需要本地磁盘,副本复制可以省掉,跨 AZ 流量费也随之消失。但对象存储的延迟一直让这个想法停留在"理论上很美"的阶段。AWS 最近发布的 S3 Files 改变了这个前提——它给 S3 加上了 NFS 文件系统接口,小文件读取延迟做到了亚毫秒级。于是一个老问题以新的面貌回来了:Kafka 能不能直接跑在 S3 Files 上?

我们在 AutoMQ 从 2023 年起就在解决这个问题——不是把 Kafka 搬到共享文件系统上,而是从存储引擎层重新设计,让 Kafka 真正运行在共享存储架构上。我们是这个领域最早探索用共享文件系统作为存储后端的团队,也是目前唯一做到生产级低延迟的 Diskless Kafka 实现。所以当 S3 Files 出现时,我们自然要评估它的可能性——以及它的边界在哪里。

S3 Files 是什么?

要回答"Kafka 能不能跑在 S3 Files 上",先得理解 S3 Files 到底做了什么。它本质上是 AWS 在 S3 之上加了一层基于 EFS(Elastic File System)的文件系统访问面——你可以通过 NFS 协议把 S3 存储桶挂载到 EC2 实例上,像操作本地文件一样读写,而 S3 始终是数据的 Source of Truth。

S3 Files 架构

这层访问面的核心设计围绕一个 128 KB 的阈值。小于 128 KB 的文件在首次访问时会被导入 EFS 高性能层,读取延迟可以做到亚毫秒到个位数毫秒;128 KB 及以上的文件则绕过 EFS,通过本地代理直接从 S3 流式读取。写入方向上,所有数据都先落到 EFS 层,再由后台异步批量同步回 S3。换句话说,S3 Files 的优化重点是小文件的低延迟读取,而不是让所有数据都常驻在高性能层。

定价模型进一步印证了这个定位。写入按流量计费 $0.06/GB,最小计费 I/O 为 6 KiB,没有预置容量选项。数据同步到 S3 后不会立刻从 EFS 淘汰,默认驻留 30 天,期间你同时支付 EFS 高性能层存储费($0.30/GB-月)和 S3 存储费用。对于读多写少的场景,这个定价是合理的。但对于持续高吞吐写入的工作负载,写入成本和 EFS 驻留费用会快速累积。

共享存储对 Kafka 的吸引力

理解了 S3 Files 的能力边界,再来看为什么大家想把 Kafka 构建在上面。传统 Apache Kafka® 是为专用服务器加本地磁盘设计的,这套架构搬到云上会产生三个不断叠加的成本问题。

最直接的是副本复制带来的跨 AZ 流量费。Kafka 通过 ISR(In-Sync Replicas)机制保证持久性,每条消息会被复制到两到三个 Broker。在多 AZ 部署中,这种复制产生大量跨 AZ 网络流量——AWS 对跨 AZ 数据传输双向收费,合计 $0.02/GB。一个写入吞吐 500 MB/s、副本因子为 3 的集群,两个 Follower 分布在不同 AZ,每秒产生约 1 GB 的跨 AZ 复制流量,仅这一项就超过 $50,000/月。

跨 AZ 副本复制

副本复制还带来了第二个问题:存算耦合。每个 Broker 在本地磁盘上管理自己的数据副本,扩展存储就意味着加机器——即使你只需要更多磁盘空间。而且必须按峰值负载加上故障冗余来预留容量,大部分时间都在为闲置资源付费。

存算耦合又进一步放大了运维复杂度。分区重分配需要在 Broker 之间物理搬迁数据,大 Topic 可能耗时数小时。Broker 故障触发漫长的恢复流程。缩容比扩容更难,因为你得先把数据搬走。

如果所有数据都在 S3 这样的共享存储上,这三个问题可以一次性解决:S3 自带 11 个 9 的持久性,不需要副本复制;Broker 变成无状态计算节点,秒级扩缩容;跨 AZ 流量降到接近零。S3 Files 有 NFS 接口、有亚毫秒延迟,看起来正好是连接 Kafka 和共享存储之间的桥梁。但真正尝试搭建这座桥梁时,会遇到几个根本性的问题。

挑战:直接把 Kafka 跑在 S3 Files 上会怎样?

Kafka on S3 Files 的挑战

持久性缺口

最符合直觉的做法是把副本因子设为 1——既然 S3 Files 提供了共享的持久化存储,一份数据就够了。问题出在 Kafka 的写入机制上。

Kafka 是一个异步 I/O 系统。Producer 发送消息并收到 ack 时,数据还在操作系统的 page cache 里,并不一定已经刷到底层存储。这是 Kafka 高吞吐的设计基础——它假设即使 Broker 在刷盘前崩溃,数据仍然安全地存在于 Follower 副本上。但在 replica=1 的 S3 Files 上,这张安全网消失了。Broker 崩溃意味着 page cache 中尚未持久化的数据直接丢失,S3 Files 的 11 个 9 持久性帮不上忙——数据根本还没到达存储层。

要堵住这个缺口,需要改变 Kafka 的写入路径:确保每条被确认的消息在返回 ack 之前就已经持久化。这不是调配置能解决的,这是存储引擎层面的重新设计。

可用性耦合

持久性问题可以通过改造写入路径来解决,但 Kafka 的高可用机制带来了另一个更深层的挑战。

Kafka 的 HA 和多副本设计紧密耦合:Broker 故障时,Controller 将 Follower 副本提升为新 Leader。这个机制的前提是存在 Follower——而 replica=1 意味着没有 Follower 可以提升。你需要一套完全不同的故障转移逻辑:让新 Broker 直接从共享存储读取数据来接管分区,而不依赖本地副本。Kafka 现有的 HA 设计天然阻止了它利用 S3 Files 内置的可用性保障。

这同样需要架构层面的重新设计——不只是写入路径,还有整个故障恢复和分区所有权的管理方式。

延迟现实

即使解决了持久性和可用性问题,延迟仍然是一道坎。S3 Files 宣传的亚毫秒延迟针对的是 EFS 高性能层上的小文件读取,而 Kafka 的核心工作负载是高吞吐的持续顺序写入——这两者的 I/O 模式完全不同。

社区已经有人在 S3 Files 上跑过 Kafka benchmark,数据很说明问题:

指标1 KiB 最大吞吐10 KiB 最大吞吐10 KiB 调优后
平均延迟31.86 ms21.86 ms21.38 ms
P500 ms1 ms1 ms
P9512 ms5 ms13 ms
P991,099 ms704 ms801 ms
P99.93,173 ms2,959 ms2,051 ms
最大值9,904 ms4,152 ms5,139 ms

中位数和 P95 看起来还行——P95 只有 5-13ms,和原生 Kafka 差距不大。但从 P95 到 P99 出现了断崖式跳跃:5ms 直接飙到 704ms,延迟放大了 140 倍。这意味着每一百次请求就有一次要等超过一秒。对于实时流处理场景——风控、实时大屏、事件驱动微服务——这种不可预测的尾延迟是不可接受的。S3 Files 并没有彻底解决共享存储的低延迟问题,相比本地磁盘上的 Kafka 仍然有明显的延迟牺牲。

成本结构

成本结构

延迟之外,S3 Files 的定价模型对 Kafka 也不友好。S3 Files 采用按流量计费——写入 $0.06/GB,小文件读取 $0.03/GB,没有预置容量选项。这和 S3 的按 API 请求次数计费是完全不同的模型。Kafka 的工作负载特征是写入和读取都需要走高性能层:Producer 写入的数据落到 EFS 高性能层,Consumer 做 Tailing Read(消费最新数据)也从高性能层读取。两端都按流量计费,成本随吞吐量线性增长。写入端还有双重流量费——数据先写入 EFS($0.06/GB),再由后台同步回 S3($0.03/GB),Kafka 的所有数据都需要同步回 S3,这笔同步费逃不掉。更隐蔽的是 EFS 高性能层的存储驻留费:$0.30/GB-月,是 S3 Standard 存储费的 13 倍,数据默认驻留 30 天才淘汰。

算一笔具体的账。一个 100 MB/s 持续写入的集群,假设消费端吞吐和写入相当(1x fan-out,即一个 Consumer Group),每天写入和读取各约 8,400 GB(100 MB/s × 86,400 秒):

费用项计算日费用
写入流量费8,400 GB × $0.06/GB$504
写入同步费(导出回 S3)8,400 GB × $0.03/GB$252
Tailing Read 流量费8,400 GB × $0.03/GB$252
日流量费合计$1,008
EFS 存储驻留费(30 天累积)252,000 GB × $0.30/GB-月$75,600/月
月总计(流量 + 存储驻留)$1,008 × 30 + $75,600~$106,000/月

这还是 1x fan-out 的保守估算。如果有多个 Consumer Group(在 Kafka 场景中很常见),Tailing Read 的流量费会成倍增长。2x fan-out 下月成本就超过 $113,000,3x 超过 $120,000。而且这还没算 S3 本身的存储费。S3 Files 的定价模型是为"读多写少、活跃工作集小"的场景设计的——Kafka 恰好相反:持续高吞吐写入,所有数据都是"活跃"的,读取端也是持续高吞吐。

这些挑战加在一起意味着什么?

持久性缺口要求重新设计写入路径。可用性耦合要求重新设计故障转移机制。延迟问题要求在对象存储之前加一层高性能写入缓冲。成本问题要求对小写入进行攒批优化。把这四项加在一起,你实际上需要的是一个全新的 Kafka 存储引擎——而这正是 AutoMQ 从 2023 年就在构建的东西。

AutoMQ:已经被验证的 Shared Storage 架构

AutoMQ 的架构分为两层。S3 是主存储层,所有数据最终都持久化在 S3 上——这是和 Tiered Storage 的根本区别。Tiered Storage 仍然把热数据放在本地磁盘上,S3 只存冷数据;而 AutoMQ 让 S3 成为唯一的 Single Source of Truth,Broker 上没有任何持久化状态(关于两者的详细对比,可以参考这篇文章)。

但直接把每条消息都写到 S3 有两个问题:S3 的写入延迟太高,而且 S3 API 调用是按次计费的——每条消息一次 PUT 请求,API 成本会随消息数线性爆炸。这就是 WAL(Write-Ahead Log)层存在的意义。

WAL 是一块固定大小的高性能存储空间,充当 S3 前面的写入缓冲。所有 Produce 请求先写入 WAL,使用 Direct IO 绕过 page cache,在返回 ack 之前就保证数据持久化——这直接堵住了上面说的持久性缺口。然后 WAL 中的数据被异步压缩、攒批,再批量上传到 S3。这个攒批过程至关重要:不是每条消息一次 S3 PUT,而是每批数千条消息一次 PUT,S3 API 成本从随消息数增长变成了随吞吐量增长,降低了一到两个数量级。

AutoMQ 架构

WAL 带来的另一个关键好处是让用户可以在延迟和成本之间做 trade-off。WAL 层是可插拔的,不同的云存储后端对应不同的延迟和成本特征:EBS/Regional EBS WAL 提供亚毫秒延迟,NFS WAL(AWS 上基于 FSx for NetApp ONTAP)提供平均 6ms、P99 约 13ms 的写入延迟。Producer 的体验和原生 Kafka 没有区别。

而且 WAL 的成本很低。它只需要一小块固定大小的存储空间——不是存全量数据,只是一个循环写入的缓冲区。对于大部分云存储的定价模型来说,这非常友好:每月几美元到几十美元的 WAL 支出,就能换来低延迟持久化、S3 API 成本优化、以及真正的无状态 Broker。

因为所有持久化状态都在 WAL 和 S3 中,Broker 是真正无状态的。一个 Broker 故障时,另一个 Broker 在秒级内接管分区映射,不需要数据迁移,Zero RPO——这解决了可用性耦合的问题。

最终效果是 Kafka on S3 Files 所承诺的一切——零跨 AZ 流量、无副本复制、弹性无状态 Broker——但没有持久性缺口、秒级尾延迟和高昂的流量成本。

S3 Files 作为 WAL:技术上可行,经济上还不成熟

既然 AutoMQ 的 WAL 层是可插拔的,S3 Files 能不能作为又一个 WAL 后端?从架构上看,答案是肯定的。S3 Files 提供 NFS 接口,底层基于 EFS 构建——而 AutoMQ 的 NFS WAL 已经支持 EFS 和 FSx for NetApp ONTAP 作为实现,技术路径是通的。

但当前的定价模型让这个方案的经济账算不过来。Kafka 不是一个轻量级的应用层服务,它是数据密集型的基础设施——AutoMQ 的一些生产客户集群吞吐超过 1 GiB/s,7×24 小时不间断写入。在这个量级下,S3 Files 的纯按量计费模型会产生惊人的费用。

以一个相对温和的工作负载为例——写入吞吐 100 MB/s、平均消息大小 4 KiB:

维度EFS(作为 WAL)S3 Files(作为 WAL)
写入定价EFS 弹性吞吐计费$0.06/GB 写入 + $0.03/GB 同步回 S3
高性能存储驻留费包含在 EFS 定价中$0.30/GB-月(默认驻留 30 天)
月成本估算(100 MB/s)~$15,500~$100,000(流量费 + 存储驻留费)

100 MB/s 已经是一个保守的数字了。如果换成 1 GiB/s 的生产集群,S3 Files 的月成本会突破百万美元——流量费和 EFS 存储驻留费都随吞吐量线性增长。核心问题在于 S3 Files 的定价模型是为"读多写少、活跃工作集小"的场景设计的,而 Kafka 恰好相反:持续高吞吐写入,所有数据都是"活跃"的。S3 Files 作为 WAL 的开销远高于直接使用 EFS,而延迟上并没有优势。

不过云存储的定价在持续演进。如果 AWS 为 S3 Files 引入预置吞吐模型或降低最小计费 I/O,这笔账可能很快就会变。AutoMQ 的架构已经为那一天做好了准备。

一套架构,适配所有云存储

S3 Files 的故事其实揭示了一个更大的趋势:云存储在加速分化。AWS 在过去两年推出了 S3 Express One Zone(个位数毫秒延迟的 S3)、S3 Files(NFS over S3)、以及对 EFS 和 FSx for NetApp ONTAP 的持续改进。GCP 和 Azure 也在各自的存储服务上走着类似的路线。每种存储服务针对不同的访问模式、成本模型和持久性保障做了优化。

可插拔 WAL 架构

AutoMQ 的可插拔 WAL 架构意味着我们不需要押注某一个赢家——每一次云存储创新都会成为 WAL 后端菜单上的一个新选项:

WAL 后端延迟多 AZ成本适合场景
EBS WAL亚毫秒单 AZ(多副本)AWS 上所有 Kafka 工作负载
Regional EBS WAL亚毫秒多 AZ(多副本)Azure / GCP / 阿里云上的生产环境(推荐)
S3 WAL百毫秒级多 AZ延迟不敏感场景(日志、监控),开源版默认
NFS WAL(AWS 上使用 EFS / FSxN)毫秒级多 AZ中等AWS 上的低延迟场景(核心交易撮合等)

用户不需要被锁定在某一种存储方案上,而是可以根据自己的延迟要求和成本预算自由选择——并且随着需求变化或云定价演进随时切换。在 AWS 上,NFS WAL 已经支持 EFS 和 FSx for NetApp ONTAP 两种实现;在 Azure 和 GCP 上,Regional EBS WAL 利用各自的多 AZ 块存储提供亚毫秒延迟。让这一切成为可能的 WAL 抽象层,从第一天起就是这么设计的。

回到最初的问题

Kafka 构建在 S3 Files 上,是个好主意吗?如果说的是把原生 Kafka 直接挂载上去——不是。Kafka 的异步 I/O、基于副本的 HA、对本地存储的假设,这些设计决策会让你回到原点:还是要管副本、管故障转移、管容量规划。共享存储就在那里,但 Kafka 的架构用不上。

但 Kafka 向 Shared Storage 架构演进的方向是确定的——经济账和运维收益太有说服力了。AutoMQ 基于 WAL 的 Shared Storage 架构已经交付了这个承诺,而且每当云存储向前迈进一步,可插拔的 WAL 层就把这次创新变成用户的一个新选项。一套架构,适配所有云存储。