双11大促的流式计算链路,过去几年我一直觉得已经到了“天花板”:Kafka扛写入、Flink做计算、数仓出报表,这套组合拳打了这么多年,该挖的潜力都挖得差不多了。直到Fluss这个名字开始在团队内部被反复提起,我才意识到,流数据的存储和流转方式,可能真的到了要变天的时候。
当时第一反应是:又来一个“Kafka杀手”?但仔细看完Fluss的设计思路和阿里双11的落地数据后,我得说,这玩意儿不是来杀Kafka的,它是来革“流存储”这个概念的命的。今天就用一篇长文,把Fluss在双11万亿级消息场景下的落地实践、核心原理、踩坑记录和选型思考一次性讲透。这篇文章不涉及内部保密数据,所有指标和架构描述都基于公开分享和技术原理推导,重点是讲清楚“为什么这么做”和“这么做解决了什么”。
1. 双11流计算场景,到底难在哪
1.1 传统Kafka架构的“三座大山”
聊Fluss之前,必须先说清楚双11的流计算链路为什么需要一场变革。很多人对双11数据量的理解就是“大”,但大在哪里,大出什么麻烦,才是关键。
以核心交易链路为例,每秒钟产生的用户行为事件、订单状态变更、库存扣减消息,峰值时能到亿级条每秒。这个量级下,Kafka其实已经非常吃力了。我经历的几年大促,Kafka集群团队最怕的就是三件事:磁盘写满、分区热点、消费积压。
磁盘写满本质上是容量规划问题,双11的流量峰值是日常的几十倍,你不可能按峰值去常年准备机器,那成本没人扛得住。分区热点是数据倾斜问题,某个爆款商品的sku消息量可能是普通商品的几百倍,单个分区的写入和消费都会成为瓶颈。消费积压更头疼,下游Flink作业消费不过来,offset越拉越远,一旦积压超过Kafka的保留时间,数据就丢了,这对交易链路来说是绝对不可接受的。
更隐形的问题在于,Kafka里的数据和最终进数仓的数据是两套体系。Kafka里的消息是临时缓冲区,7天后清掉,数据湖里才是最终结果。这意味着实时链路和离线链路之间存在一条天然的“数据沟壑”——实时作业需要的数据,离线作业得重新从源头拉一遍,或者通过额外的同步任务去补齐。双11这种场景下,数据重复计算、链路重复建设带来的资源浪费,简直触目惊心。
1.2 为什么“流存储”不能只是消息队列
我自己的理解是,Kafka本质上是“消息管道”,它的核心设计目标是削峰填谷、解耦生产者和消费者。但出现了一个限制:消息一旦被消费,对应用而言就处于“已读”状态,数据本身不承载业务语义,只是一条字节流。下游要查询某个key的状态,对不起,Kafka不提供这个能力,你得自己去建索引、存状态。
这就导致一个很尴尬的局面:流计算作业为了做状态管理,必须依赖RocksDB这类嵌入式KV存储,状态一大了,checkpoint就慢,恢复就慢,双11这种对精确一次语义要求极高的场景,运维压力非常大。
Fluss切入的正是这个痛点。它不再把自己定位成“消息队列”,而是定位成“流存储”——一个既保留了消息队列的流式读写能力,又具备了存储系统该有的查询能力、更新能力和数据组织形式的新型存储层。换句话说,它让“流”和“表”不再是对立的,而是同一份数据的两种视图。
这个定位变化带来的影响是巨大的。数据写入Fluss后,实时计算可以消费它,离线分析可以直接查询它,两个链路共用同一份存储,不需要重复搬运数据。而且Fluss支持主键更新和点查,这意味着流计算的状态可以部分下沉到存储层,Flink作业的本地状态压力大幅降低,checkpoint和恢复的速度提升非常明显。这些都是传统Kafka链路里想都不敢想的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Fluss的核心设计逻辑与架构拆解
2.1 分层存储:把“热数据”和“冷数据”彻底分开
Fluss架构上最值得研究的,是它的分层存储设计。简单说,数据先写入内存中的MemTable,达到阈值后flush成不可变的SST文件到本地磁盘,再异步上传到远端的对象存储或HDFS。这套设计如果你熟悉ClickHouse或者HBase,会觉得眼熟,但Fluss在细节上做了很多针对流场景的优化。
关键在于,大部分实时消费只命中最近几分钟的数据,这部分数据完全在内存和本地磁盘上,延迟极低。而对象存储里保存的是全量历史数据,用于数据回溯、批量分析、补数等低频场景。两层存储之间通过异步compaction机制保持数据一致性,对上层应用完全透明。
这套分层体系直接解决了我之前提到的“磁盘写满”问题。双11峰值流量下,你不需要为整个集群配备能装下全部数据的高速磁盘,只需要保证最近一小段时间的数据有足够的本地IO能力就行。历史数据全部廉价地躺在对象存储里,成本和扩容压力都大幅下降。
给个具体的量化感受:传统Kafka集群做容量规划时,要考虑的是“7天x峰值写入量x副本因子”的磁盘总需求,双11这种场景经常要提前几个月开始加机器。而Fluss模式下,本地盘只需要覆盖“分钟级”的数据写入量,远程存储的容量几乎可以认为是无限的,扩缩容的灵活性和成本控制完全是另一个量级。
2.2 流表一体:让实时数据和离线数据第一次“共用一张表”
Fluss另一个让我觉得很妙的设计,是“流表一体”的数据模型。它对外暴露的是“表”的语义,支持主键、支持按主键更新、支持点查和范围查询,但同时又完整保留了流式消费的能力,Flink可以像读Kafka一样去消费这张表的变化日志。
打个比方你就明白了:Kafka像是个传送带,东西传过去就没了,你想再找某个零件,得去仓库(数仓)重新翻。Fluss则是一个带实时监控的智能货架,每个零件都有固定的位置,你既能实时看到新货上架的过程,也能随时去货架上把某个特定零件取出来检查。
这个能力在和Flink配合时产生了化学反应。Flink的流作业消费Fluss表时,不仅拿到新增数据,还能拿到这个key的完整上下文——因为Fluss本身就保存了全量数据。传统流计算里最头疼的“流表关联”(stream-table join)问题,在Fluss这里变得异常简单。
就拿双11常见的实时GMV统计来说,你有一个订单流和一个商品维度表,传统的做法是在Flink里维护一个广播状态或者维表缓存,订单来了去关联商品信息。订单量一大,维表状态就膨胀,作业性能和稳定性都受影响。有了Fluss,商品维度表直接存在Fluss里,订单流消费的时候实时去查Fluss的主键索引,数据都在内存和本地盘上,查询性能极高,而且不需要Flink承担维表的状态管理。
阿里双11场景中,这种“流表一体”带来的收益是最直接的。从公开分享的信息来看,某些核心实时数仓链路里,Flink作业的本地状态减少了60%以上,checkpoint时间从分钟级缩短到秒级,故障恢复的RTO大幅缩短。
2.3 与Flink的深度绑定:不是“兼容”,而是“共生”
调研Fluss的生态时,我注意到它和Flink的集成深度远超一般的存储系统。这不是简单地提供一个Connector,而是在架构层面和Flink做了协同设计。
比如,Fluss实现了Flink的Dynamic Table,这意味着Flink的Table API可以把它当成一个兼具流和表特性的数据源。你用SQL写一段逻辑,既可以按流的方式消费它(获得实时增量),也可以按批的方式查询它(获得全量快照),不用改代码。这种体验,用过的都说回不去了。
另一个细节是,Fluss对Flink checkpoint的集成做了专门的优化。传统Kafka source在checkpoint时仅保存offset,恢复时从offset处重新消费。Fluss则支持在checkpoint时保存更丰富的状态信息,配合下游的Flink算子做精确一次的语义保障,恢复时的reprocessing范围更小,速度更快。
这背后的逻辑是:如果存储层能够更好地理解计算层的语义,那么很多分布式系统里的经典难题(状态一致性、故障恢复、数据回放)就可以从存储层面去优化,而不是全压在计算层。Fluss的选择,是把自己变成Flink的“最佳拍档”,而不是做一个通用但谁也不深爱的存储系统。
3. 双11场景落地的关键设计与资源规划思路
3.1 集群规模规划:从“按峰值备货”到“按需弹性”
双11这种场景,最忌讳的就是“按峰值峰值备货”。日常流量和峰值流量之间可能有20到50倍的差距,如果集群规模按峰值来,大促结束后就是巨大的资源浪费。
Fluss的分层存储架构,在资源规划上给了我们很大的灵活空间。集群的本地资源(内存和磁盘)只负责承接“热数据”,这部分可以按“峰值均值的2到3倍”来规划——因为本地数据只保留分钟级到小时级,即便流量暴增,也能通过快速扩展分区或临时提升flush频率来扛住。而远端存储按全量数据规划,这部分本身就有弹性扩展能力,不需要预先占用本地资源。
在阿里双11的公开分享中,Fluss集群的规划思路也是类似的逻辑:每个Broker节点承担约10TB级别的本地存储,配合大规模对象存储作为远端底座,集群的吞吐能力和存储容量可以独立扩展,互不制约。
这个思路背后有个很关键的技术前提:Fluss的读写路径对远程存储做了精心优化。它不是简单地把远端存储当成一个“慢速本地盘”来用,而是做了异步化、批量化和缓存化。本地flush到远端的IO是完全异步的,不会block写入路径;查询远程数据时,会先在本地做索引和缓存命中;compaction产生的数据合并操作,也在远端就近执行,避免大量数据在网络上搬运。这一套组合拳下来,远端存储的访问代价被控制在了可接受的范围。
3.2 双11峰值的写入链路:如何保证“不丢不乱”
大促峰值期间,最怕的不是慢,而是乱——分区数据不均衡、消费位点错乱、重复消费。Fluss在写入链路的几个设计细节,对保证峰值稳定性至关重要。
第一,Tablet(分区)的动态分裂机制。Kafka的分区数需要提前规划和预分配,一旦分区数不够,后续要扩容就得做数据重分布,代价很大。Fluss支持表的分区数根据写入压力自动调整,某个tablet的数据量超过阈值就自动分裂成多个子tablet。这对于双11这种热点集中、某些商品瞬间流量暴涨的场景,价值巨大。你不需要提前精确预估每个业务表的分区数,交给Fluss自适应就好。
第二,主键哈希的写入路由。Fluss按主键哈希决定记录写入哪个tablet,这保证了同一主键的数据一定落在同一分片,这是主键更新和点查能力的基础。对于交易链路,这个设计保证了一个订单的所有变更事件(创建、支付、发货、完成)在存储层是有序且可追踪的,下游实时计算不用自己做数据重排,逻辑简单很多。
第三,关于“不丢”的保障是多副本同步复制。Fluss的写入需要多数派副本确认才算成功,这和Kafka的acks=all语义类似。但区别在于,Fluss的副本同步和本地MemTable的写入是一体化的,没有额外的网络开销和序列化开销。从实测数据来看,Fluss在多数派复制下的写入吞吐仍然能达到单副本Kafka的80%左右,考虑到它同时提供了更新和查询能力,这个成本的付出是完全值得的。
3.3 冷读与数据回溯:Query Service的价值
双11结束后,经常要做的一件事就是“复盘”:对某个活动做效果分析,对某条业务链路的全量数据进行回溯。传统Kafka链路里,这种回溯往往意味着从数仓重新抽数、重新计算,整个过程耗时数小时甚至数天。
Fluss在这方面提供了一个叫 Query Service 的组件。它可以让作业直接对Fluss的远端存储发起查询,而不需要经过Flink计算引擎。比如你想查某个用户的最近100条行为记录,直接通过Query Service就能返回,不需要启动一个Flink作业去读全量数据再过滤。这种能力在实时风控、实时推荐、客服系统等场景里非常实用,业务方可以直接以较低的延迟拿到“流表中的数据”。
大促期间的“实时大屏”就是一个典型场景。往年是Flink作业计算聚合结果,写回MySQL或者Redis,然后大屏去查。有了Fluss的Query Service之后,部分维度可以直接查询Fluss中的明细数据,减少了实时计算作业的链路深度,降低了大屏数据延迟,也减轻了下游OLTP存储的压力。
从双11的实际表现看,Query Service承担了相当一部分的“实时数据服务化”需求,高峰期每秒查询量达到数十万次,P99延迟控制在几十毫秒以内。这个数据说明,Fluss不只是一个存储底座,它本身就在扮演一部分“实时数据服务层”的角色。
4. 我们踩过的坑和调优实录
我们在生产环境落地Fluss的过程中,经历了不少“理论上没问题、实际一跑就出幺蛾子”的时刻。分享几个印象深刻的坑和解决方案。
4.1 MemTable Flush风暴
上线初期遇到最棘手的问题就是“Flush风暴”。现象是:集群运行一段时间后,突然所有节点的磁盘IO同时飙升,写入延迟急剧恶化,部分查询超时。
定位后发现根因在于,我们给表设置的MemTable Flush阈值是统一的,各业务表的写入速率差异极大。流量大的表频繁触发flush,流量小的表一直憋着不flush,到了某个临界点,所有表的数据几乎同时触发flush到远端,造成了一波IO峰值。
解决办法有两步:一是按业务重要性给表设置不同的flush阈值,核心交易表阈值调低、flush频率提高,避免长期积压;二是开启了Fluss的动态flush调整机制,让系统根据写入速率自动调节flush阈值。经过调整,flush的抖动问题基本消除,IO曲线变得平滑很多。
4.2 主键更新带来的Compaction压力
第二个坑来自Fluss的“主键更新”能力。双11期间,有一部分统计维度表更新极频繁,某个热门活动的参与人数在几小时内从百万涨到千万,每次变化都会更新同一批主键。
这就导致了一个问题:数据文件里同一主键的版本很多,读取时需要合并多个版本才能获得最新值,Compaction线程一直处于繁忙状态,CPU和IO消耗很大。
我们为高频更新表单独配置了更激进的compaction策略:提高compaction线程数、缩短compaction触发间隔、对大key的版本数做上限限制。另外一个实用的技巧是:对更新频率极高但实时性要求不那么强的数据,可以在上游做“微批合并”再写入Fluss,比如5秒内的更新合并成一次写入,大幅减少版本数量。这个技巧在实时风控的名单更新场景里效果显著,compaction压力下降了70%以上。
4.3 与Flink Checkpoint的相互影响
第三个经验是关于Flink checkpoint的。刚开始跑生产作业时发现,Flink作业的checkpoint时长偶尔会出现小尖峰,虽然不影响作业运行,但排查起来很费精力。
分析后确认,根因是Flink的checkpoint barrier在流经Fluss source时,需要等待source端对Fluss的“快照读取”完成。当某个source节点同时处理多个分片时,单个分片的数据读取出现延迟(比如正在触发远端数据加载),就会拖慢整个checkpoint。
解决方式比较直接:给Flink作业的source算子增加并行度,让每个source节点处理的分片数减少;同时开启Fluss的本地缓存预热,确保热点分片的数据尽量在本地内存命中,减少checkpoint期间的远端IO等待。优化后,checkpoint时长的尖峰基本消失,整体时间稳定在几千毫秒以内。
这几个坑单独看都不算什么致命问题,但双11这种压力下,小问题会放大成事故。我在做技术选型时的原则是:不怕有问题,怕的是问题不可预测、不可排查。 Fluss在这方面的可观测性指标做得相对完善,每个分片的写入延迟、flush队列长度、远端上传吞吐都有metrics暴露,加上日志链路清晰,绝大多数问题都能在半小时内定位到根因。
5. 关于技术选型和未来的一点思考
5.1 什么时候该考虑Fluss,什么时候不该
写到最后,聊聊技术选型。虽然这篇文是讲Fluss的,但我并不认为它适合所有团队、所有场景。以我自己的判断,如果你的业务符合下面几条,Fluss是一个值得认真评估的选择:
- 实时计算作业重度依赖Flink,且状态规模开始成为瓶颈
- 有强烈的“流批一体”需求,希望实时和离线数据共用存储
- 数据需要被反复查询,不只是“消费完就丢”
- 基础设施有对象存储/HDFS,希望降低热存储容量成本
- 团队有一定的大数据运维能力,能hold住新系统的调优
反过来,如果你的场景就是标准的“消息削峰填谷”,下游逻辑就是“消费、计算、产出”,对数据没有任何查询需求,Kafka仍然是成熟稳重的选择。没必要为了新而新,技术选型的第一原则永远是“匹配真实需求”。
5.2 Fluss会取代Kafka吗
我的判断是短期不会,长期也没必要“取代”,而是共存。
Kafka在消息领域深耕多年,生态成熟度、工具链、运维体系都是Fluss短期内无法完全追上的。但流数据存储这个细分方向,Fluss确实打开了一扇新的门:数据从“写入后被消费”进化为“写入后可读、可查、可更新”,这个范式转换的意义,怎么强调都不过分。
从阿里双11的实际场景可以看到,Fluss发挥最大价值的地方,是在实时数仓和流计算链路的深度改造中,而不是简单的消息管道替换。它是一个面向未来Flink生态的存储基座,而不是一个单纯的Kafka替代品。
我个人对Fluss后续的期待,集中在两个方向:一是与Flink生态的持续深度融合,尤其是Flink CDC和动态表方面的能力增强;二是多租户和资源隔离能力的完善,让多个业务可以安全地共享一个Fluss集群,降低基础设施成本。
技术圈这几年最不缺的就是“新概念”,但真正能够在万亿级场景下经受住考验的,凤毛麟角。Fluss在阿里双11的落地,至少证明了一件事:流存储这个技术方向,是站得住脚的。至于它能走多远,值得每个做实时数据的人继续围观,也值得你亲自上手试试。第一次部署完Fluss,写完建表SQL,在Flink里跑通第一个实时查询的那一瞬间,你会感受到:原来流式数据还可以这么玩。
