1. 流批一体概述:流处理与批处理的概念、Lambda架构与Kappa架构、流批一体的价值与挑战

1.1 流处理与批处理:两个世界的碰撞

做数据这行久了,你会发现一个有意思的现象:批处理和流处理,就像一对性格迥异的兄弟。批处理是那个沉稳、靠谱的老大哥——你给它一堆数据,它慢慢算,算完了给你一个准确的结果。流处理呢,是那个反应快、坐不住的小弟——数据一来它就处理,延迟低,但结果可能没那么精确。

批处理的核心逻辑,说白了就是「攒一批,算一次」。我早年做离线数仓的时候,每天凌晨跑一次ETL,把昨天的数据算好,第二天早上业务方就能看到报表。这种方式的好处是简单、可靠,出错了重跑一遍就行。但坏处也很明显——你永远只能看到「昨天」的数据。

流处理就不一样了。它是一条永不停歇的传送带,数据一条条进来,一条条处理。我在做实时风控项目时遇到过这样的场景:用户刚点了一下支付按钮,系统必须在几百毫秒内判断这笔交易是不是欺诈。批处理?等它跑完,钱早就没了。

来,我们直观对比一下:

维度 批处理 流处理
数据范围 全量历史数据 实时到达的数据
延迟 分钟级~天级 毫秒级~秒级
计算模型 一次性触发 持续计算
准确性 精确(可重跑) 近似(有窗口机制)
典型场景 日报、月报、离线分析 实时监控、风控、推荐
代表技术 Hive、Spark SQL Flink、Kafka Streams

核心矛盾:批处理追求「准」,流处理追求「快」。但业务方往往两个都想要——既要实时看到数据,又要保证最终结果准确。这就是流批一体要解决的问题。

1.2 Lambda架构:一个不得已的折中方案

大概十年前,大家开始意识到:光靠批处理不行,光靠流处理也不行。于是Nathan Marz提出了Lambda架构。这个架构的思路很直白——既然两个都要,那就两套都做。

Lambda架构分三层:

  • 批处理层(Batch Layer):存储全量原始数据,定期计算,生成准确的批视图。我习惯用Hive或Spark SQL来做这层,每天凌晨跑一次全量重算。
  • 速度层(Speed Layer):处理实时增量数据,生成近似的实时视图。这层我一般用Flink或Storm,延迟控制在秒级。
  • 服务层(Serving Layer):把批视图和实时视图合并,对外提供统一查询接口。

听起来挺合理对吧?但我在实际项目中踩过不少坑。最头疼的问题是:同样的业务逻辑,你要在批处理和流处理里各写一遍。而且这两套代码用的框架不同、API不同、甚至语义都可能不同。批处理里一个简单的SUM,到了流处理里要考虑窗口、水位线、乱序数据……维护成本直接翻倍。

我曾经在一个金融项目中维护Lambda架构,批处理用Spark SQL,流处理用Flink。每次业务逻辑变更,两个团队要同时改代码,联调一次至少三天。更崩溃的是,有时候批处理和流处理算出来的结果对不上,排查问题就像在迷宫里找路。

避坑指南:我曾经因为Lambda架构中批流结果不一致,被业务方追着问了一个星期。后来发现是窗口边界定义不同——批处理按自然天切分,流处理按事件时间+水位线切分,差了8个小时。所以如果你非要用Lambda架构,一定要统一时间语义。

下面这张图展示了Lambda架构的数据流转:

Lambda 架构数据流转图 实时数据流 全量历史数据 速度层 (Speed Layer) Flink / Kafka Streams 批处理层 (Batch Layer) Spark SQL / Hive 实时视图 批视图 服务层 (Serving Layer) 统一查询接口 ⚠ 痛点: 同一业务逻辑需要在速度层和批处理层各实现一次,维护成本高,且两套结果可能不一致。

1.3 Kappa架构:简化,再简化

Lambda架构的痛点太明显了。后来Jay Kreps提出了Kappa架构,思路很干脆:既然流处理能处理全量数据,为什么还要保留批处理层?

Kappa架构只有一层——流处理层。所有数据都走流处理管道,历史数据通过「重放」的方式重新处理。你想想看,Kafka里保存了全量日志,需要重算的时候,把消费偏移量调到最早,重新跑一遍流作业就行了。

我个人比较喜欢Kappa架构的简洁性。一套代码,一套逻辑,不用再纠结批流不一致的问题。但这里有个前提:你的流处理引擎必须支持状态管理和精确一次语义。Flink在这点上做得不错,我最近几个项目都用的Flink SQL + Kafka,基本能覆盖大部分场景。

不过Kappa架构也不是银弹。它的问题在于:

  • 历史数据重放耗时:如果数据量很大,重放一次可能要跑几个小时甚至几天。我有个朋友在电商公司,他们的Kafka集群存了半年的数据,每次重放都要跑一整天。
  • 状态管理复杂:流处理的状态后端要能扛住全量数据,Checkpoint和Savepoint的机制必须可靠。
  • 临时性分析不友好:有时候业务方就想跑个临时SQL看看历史数据,用流处理去重放全量数据,有点杀鸡用牛刀的感觉。

我的建议:如果你的业务场景以实时为主,历史数据重放频率不高,Kappa架构是更好的选择。但如果你的批处理任务很重(比如每天跑几百张报表),Lambda架构可能更实际。没有完美的架构,只有适合的架构。

1.4 流批一体的价值:为什么我们要统一?

聊完了两种架构,你可能会问:那流批一体到底是什么?

说白了,流批一体就是用同一套引擎、同一套SQL、同一套元数据,同时支持批处理和流处理。你写一条SQL,既可以在批模式下跑全量历史数据,也可以在流模式下跑实时数据,结果语义保持一致。

它的价值体现在几个方面:

  1. 降低开发成本:不用再维护两套代码。我之前的Lambda架构项目,一个简单的数据清洗逻辑,批处理和流处理各写了200行代码,加起来400行。用流批一体,一条SQL搞定。
  2. 保证结果一致性:批和流用同一套计算逻辑,不会出现「批处理算出来100万,流处理算出来98万」的尴尬情况。
  3. 简化运维:只需要部署一套引擎,监控一套指标,出问题了排查链路也短。
  4. 灵活切换:同一个作业,可以今天跑批模式做历史回溯,明天切流模式做实时计算,不用改代码。

核心价值一句话:流批一体让「批」和「流」不再是两个世界,而是同一枚硬币的两面。

1.5 流批一体的挑战:理想很丰满,现实很骨感

虽然流批一体的愿景很美好,但落地的时候,你会发现有不少坑要填。

第一个挑战:语义一致性。批处理是确定性的——同样的输入,永远得到同样的输出。但流处理不是,它受数据到达顺序、窗口边界、水位线等因素影响。怎么保证批和流在语义上等价?这是最核心的难题。

第二个挑战:状态管理。批处理是无状态的,跑完就结束了。流处理是有状态的,要维护窗口聚合、Join缓存、累加器等。流批一体引擎需要同时支持这两种模式,状态后端的选型和调优很关键。

第三个挑战:性能差异。批处理可以批量读取、批量计算,利用列式存储和向量化执行。流处理是逐条处理,延迟要求高。同一套引擎要兼顾两种模式,优化方向可能互相冲突。我记得在调优Flink批模式的时候,发现它的批处理性能比Spark SQL差了30%左右,后来调整了内存管理和调度策略才追回来。

第四个挑战:生态兼容。现有的数据仓库、BI工具、调度系统都是围绕批处理设计的。流批一体要融入这个生态,需要做不少适配工作。

避坑指南:我曾经在一个项目中尝试用流批一体替换原有的Hive + Spark Streaming架构。结果发现,原有的Hive UDF在流引擎里跑不了,需要重写。所以迁移之前,一定要先评估UDF和自定义函数的兼容性,不然光改UDF就能改到崩溃。

1.6 本章小结

这一章我们聊了批处理和流处理的核心差异,回顾了Lambda架构和Kappa架构的演进历程,也分析了流批一体的价值和挑战。你会发现,从Lambda到Kappa,再到流批一体,本质上都是在解决同一个问题:如何用更少的成本,同时满足实时和准确的诉求。

接下来的章节,我们会深入具体的实现细节。我会带着你从SQL的角度,一步步拆解流批一体的核心技术——包括时间语义、窗口机制、状态管理、容错恢复,以及如何用统一的SQL语法同时驾驭批和流。这些都是我在实际项目中踩过的坑、总结的经验,希望能帮你少走弯路。