1. 状态复用概述

1.1 流批一体架构的演进脉络

聊状态复用之前,得先搞清楚流批一体到底是怎么来的。我入行那会儿,流处理和批处理还是两套完全独立的系统——批用 Hive/Spark SQL,流用 Storm/Flink。数据口径对不上,运维得养两个团队,想想都头疼。

后来大家发现,其实流和批本质上是同一件事:都是对数据的处理,只是时间窗口不同。批处理看的是「过去一整天的数据」,流处理看的是「刚到的这一条」。那为什么不能用同一套代码、同一套引擎来搞定呢?

流批一体的演进大致经历了三个阶段:

阶段 代表技术 核心特点 我遇到的坑
Lambda 架构 Storm + Hive 流批两套代码,结果合并 口径对不上,半夜被叫醒修数据
Kappa 架构 Kafka + Flink 统一数据管道,重放即批 状态膨胀,Checkpoint 经常失败
流批一体 Flink SQL / Spark Structured Streaming 同一算子,同一状态,同一语义 状态复用成了新瓶颈

说白了,Lambda 架构就是「两条腿走路」,一条腿跑批,一条腿跑流,最后把结果拼起来。但拼的时候经常发现——咦?批算出来的 UV 和流算出来的 UV 怎么差了 5%?

Kappa 架构好一些,所有数据都走消息队列,批处理就是从头重放一遍流数据。但你想想看,重放几天的数据,状态得有多大?我有个项目,Kafka 里攒了 7 天的点击流,重放一次,Flink 的状态后端直接撑爆了。

到了真正的流批一体架构,核心思路变了:不再区分流作业和批作业,而是同一个作业可以跑在「流模式」或「批模式」下。状态不再是流处理的专利,批处理也需要状态——比如去重、累计、窗口聚合。

核心转变:状态从「流的附属品」变成了「流批一体的基础设施」。谁把状态管好了,谁就能在流批一体里占住身位。

流批一体架构演进路线 Lambda 架构 流:Storm 批:Hive/Spark 两套代码,结果合并 Kappa 架构 统一管道:Kafka 引擎:Flink 重放即批处理 流批一体 同一算子 + 同一状态 流/批模式切换 状态复用成关键 演进本质 从「两套系统各自为政」→「统一管道但状态膨胀」→「状态成为一等公民」 状态复用是流批一体从「能用」到「好用」的分水岭

1.2 状态复用的核心价值

好,架构演进说完了。那状态复用到底解决了什么问题?我直接说结论:它让流批两种模式共享同一份状态数据,不用重复计算、不用重复存储、不用对口径。

具体来说,核心价值有三点:

  • 计算不重复——批处理可以直接复用流处理已经算好的中间状态。比如实时去重用的布隆过滤器,批处理不用重新构建,拿来就用。
  • 存储不浪费——状态后端只需要维护一份数据。我见过最夸张的项目,流和批各存了一份 500GB 的维表,纯属浪费。
  • 语义保一致——流和批用同一个状态处理器,算出来的结果天然对齐。再也不用半夜对口径了,你想想看,这得少挨多少骂。

我的经验:状态复用最大的收益其实不是性能,而是「信任」。当业务方发现流和批出的报表数字一模一样时,他们就不再质疑你的数据质量了。信任这东西,比任何优化都值钱。

举个例子。我在做用户画像项目时,需要实时计算「过去 7 天活跃用户」。流作业每来一条事件就更新状态,批作业每天凌晨跑一次全量修正。如果没有状态复用,批作业得从头扫描 7 天的原始日志,耗时 40 分钟。用了状态复用后,批作业直接读取流作业维护的 HyperLogLog 状态,3 秒搞定。

说白了,状态复用就是「前人栽树,后人乘凉」。流处理把树栽好了,批处理直接去树底下乘凉就行。

1.3 适用场景分析

状态复用不是银弹,不是什么场景都适合。我踩过坑,也见过别人踩坑。下面这几个场景是我认为最适合上状态复用的:

场景 典型需求 状态复用方式 避坑提醒
实时去重 + 批量回溯 UV 计算、曝光去重 复用布隆过滤器 / 位图状态 布隆过滤器有误判率,批处理能否接受?
累计指标 + 离线修正 GMV、DAU 等累计值 复用 ValueState / MapState 状态 TTL 要设对,否则批处理读到过期数据
维表关联 + 批流同源 用户维表、商品维表 复用广播状态 / CEP 状态 维表更新频率高时,状态一致性难保证
窗口聚合 + 历史重算 小时级/天级聚合 复用窗口状态 / 聚合累加器 窗口大小不一致时,状态切分要小心

反过来,有些场景我建议你谨慎:

  • 状态太大——单作业状态超过 100GB,复用反而拖慢批处理。我曾经有个项目,状态 300GB,批处理读状态花了 15 分钟,还不如直接扫原始数据。
  • 状态频繁变更——比如维表每秒更新一次,流作业的状态一直在变,批处理读到的可能是「中间态」,结果不准。
  • 批处理要求精确一次——流处理的状态可能因为故障回滚,批处理如果复用了「脏状态」,数据就歪了。

我曾经踩过的坑:有个实时风控项目,流作业用状态维护了用户的「历史行为序列」。批处理想复用这个状态做离线分析,结果发现状态里存的是「部分序列」——因为流作业为了性能做了截断。批处理拿到的数据不全,分析结果直接作废。后来我们加了一个「状态完整性标记」,批处理先检查标记再决定是否复用。

1.4 状态复用的技术选型考量

选不选状态复用,我一般会问自己三个问题:

  1. 流和批的语义能对齐吗?——比如去重逻辑、窗口边界、时间语义是否一致。对不齐就别复用,硬上只会更糟。
  2. 状态后端能扛住并发吗?——批处理读状态时,流处理还在写。RocksDB 还好,如果是内存状态后端,并发读写容易出问题。
  3. 运维成本能接受吗?——状态复用意味着状态要「跨作业共享」,这就涉及状态的生命周期管理、权限控制、版本兼容。小团队慎入。

我个人习惯是:先跑通流处理,稳定运行一周后,再考虑状态复用给批处理。别一上来就搞复用,流处理自己还没稳呢,批处理再一掺和,排查问题能让你怀疑人生。

一句话总结本章:状态复用是流批一体从「架构概念」走向「工程落地」的关键一步。它解决的是「算过的数据不再重算」的问题,但前提是——你得把状态当回事,认真设计、认真管理。