前阵子和一个做数据平台的同行聊实时数仓他上来就吐槽业务方天天要实时大屏、实时看板昨天的数据根本不看就要现在的可离线数仓跑完一批已经凌晨三点第二天一早开会还是被问昨晚实时和今天离线为什么差几十万。这个场景我太熟了过去几年我见过太多团队被这类问题推到墙角然后开始研究Lambda、Kappa再到现在的湖仓一体。可以说实时数仓架构的每一次演进都不是哪个架构师拍脑袋想出来的而是被真实业务需求一步步逼出来的。这篇文章算是我对这几年的一个复盘。围绕从Lambda到湖仓一体这条演进线把各种架构出现的背景、解决了什么问题、又付出了什么代价以及真正落地时需要注意的细节一次性讲清楚。不管你是刚接触实时数仓还是已经在维护一套Lambda体系、正在纠结要不要往湖仓一体迁这篇文章应该都能给你一些参考。1. 需求变化才是架构演进的真正推手——从T1到分钟级可用1.1 为什么离线链路突然不够用了很多人把实时数仓当成一个技术话题来聊我觉得它的起点其实是业务话题。早几年大多数公司的数据分析节奏就是T1今天看昨天的报表决策也是按天做的。那时候离线数仓跑天级调度凌晨两三点出结果早上十点开会看数完全够用。Hive数仓加Spark批处理就是那个时代的标准答案。但后来业务变了。精细化运营、实时风控、大促实时大屏、用户行为实时反馈这些需求有一个共同点它们不看昨天的总量要看当前的状态。举个例子运营想看此刻正在参与活动的用户数风控想看过去五分钟内异常高频的登录行为这些用T1根本解答不了。风控场景尤其典型——等第二天跑完批资金损失已经发生了。所以从根上说是业务时效性要求从天级变成了分钟级甚至秒级离线链路才显得不够用了。这个需求变化是所有后续架构演进的最底层推手。1.2 实时需求的第一版长什么样很多团队的第一个实时方案其实是临时拼凑出来的。业务方说我要实时数据开发同学第一反应就是把线上产生的消息往Kafka里放然后写一个Flink任务做简单聚合最后把结果写到Redis或者一个OLAP存储里前端大屏直接查。这个方案能做出来但问题很快就来了它没有规范没有分层没有统一的数据口径。这边Flink实时统计的GMV和离线数仓凌晨跑出来的GMV经常对不上业务方拿着两个数分别问你还要一个个解释为什么不一样。今天加一个指标、明天加一个维度每条链路都是手工作坊式的独立开发。结果就是数据团队被实时需求推着走走一步补一步系统越来越乱。现在回头看这种离线链路旁边长出一条野生实时链路的形态其实就是Lambda架构的雏形只不过当时没人给它取名字更没人意识到两条链路并行这件事注定要带来长期的维护负担。2. Lambda架构的辉煌与代价一套批、一套流换来时效与一致性之间的长期拉扯2.1 Lambda解决的核心矛盾全量准确与快速响应的并存等实时需求多到一定程度数据团队开始正视一个问题能不能有一套设计过的架构来同时满足数据准确和响应够快这两个看似矛盾的要求Lambda架构正是冲着这个来的。Lambda的思路很简单也很经典数据进来之后兵分两路。一路进批处理层用Hive/Spark做全量计算保证最终准确、可回溯另一路进速度层用Flink/Kafka Stream做实时计算保证低延迟产出。最后在服务层把两边的结果合并对外提供统一的查询。这样设计等于把准确和快分开治理各有各的引擎各算各的账。我记得当时给某电商类业务做大促实时大屏用的就是Lambda思路速度层用Flink做分钟级聚合实时展示累计订单量批处理层凌晨用Spark重算一遍全量明细。白天看板是实时的第二天早上看板自动切到离线修正后的数据整体体验确实比以前只有一个野生实时链路时顺滑多了。因为有了明确的分层和职责团队终于知道每条链路的定位是什么出问题也知道去哪里排查。2.2 两套链路对不齐账一次真实排查流程复盘但Lambda的甜蜜期通常不会持续太久。当两条链路都会产出同一个指标时对不齐账就成了家常便饭。我印象最深的一次排查过程现在想起来还很有代表性。某天早上业务方拿着前一天的实时累计GMV和凌晨离线跑出的GMV来找我们两个数差了十二万。第一反应是查速度层——Flink聚合逻辑和离线口径是不是一致查了半天发现Flink用的是事件时间离线用的是处理时间本来就是两种语义差一点正常。但十二万这个量级显然不是正常误差。接着查批处理层发现某类订单在凌晨被ERP系统做了状态回改从已支付变成了退款中离线任务按最新状态重算了GMV把这几笔扣掉了而Flink实时链路写的是支付成功即计入根本不知道后面状态会变。也就是说两条链路的业务口径其实已经不一致了只是因为平时差异小没人注意那天集中爆发而已。最终修法很粗暴实时链路也订阅状态变更消息对已计入GMV的订单做冲减。但这件事让我意识到Lambda架构的根本痛点不是技术选型而是同一份业务逻辑在两套系统里各写一遍必然会出现口径漂移。今天你在Flink里加了一个判断逻辑明天不一定记得在Spark里同步两边都是人在维护人的记忆是会出错的。2.3 什么阶段该继续用Lambda什么阶段该考虑换我不是要全盘否定Lambda。到今天很多团队的离线数仓体系已经很成熟实时需求也确实是部分指标才需要这种情况下继续用Lambda完全没问题。你只需要接受一个事实两套链路偶尔对不上账需要靠人肉盘点、规则校验去兜底。但如果出现下面几种情况就说明Lambda的负担快要超过收益了实时指标越来越多几乎每个核心指标都要求实时对得上离线对口径一致性的要求是审计级的不允许差不多;团队每天有大量时间花在排查批流差异而不是开发新需求上存储和计算成本因为双链路翻倍老板开始问为什么同样的数据要跑两遍。我们团队就是在这些信号全部出现之后才开始认真调研Kappa和湖仓一体的。注意这中间不是突然有一天觉得Lambda过时了而是运维成本堆到了一个临界点逼着你必须重新算一笔账。3. Kappa的简化尝试一套引擎重跑全量为什么没有成为终极答案3.1 Kappa的核心理念日志即数据流即批既然Lambda双链路的核心痛点是批和流各写一遍那能不能反过来——只用一套流处理就把所有问题解决了这就是Kappa的思路。Kappa的设想是把Kafka当作全量数据日志留存所有真实数据都以消息的形式存在Kafka里计算逻辑只写一套Flink任务需要重算历史的时候不跑Spark批任务而是启动一个新的流任务从Kafka的旧位点开始重新消费、重新计算。理论上你今天改了一个统计口径明天启动一个新Flink作业从三天的位点重放就能得到按新口径算出来的结果。这个思路最吸引人的地方是统一一套代码、一套引擎、一套口径从设计上消灭批流不一致。相比Lambda那种双轨制它显然更优雅。我当时也认真考虑过把团队的核心指标链路切到纯Kappa模式。3.2 生产环境里的三个现实问题真要在生产环境常年跑纯Kappa你会遇到几个绕不开的问题。第一个是Kafka保留全量数据的成本。Lambda里Kafka默认只保留最近7天的数据更早的明细由批链路负责重算Kappa则要求Kafka保存足够长时间的数据比如30天甚至更久方便随时重放。消息体不是普通日志是经过解析和清洗的业务数据每天几十亿条长期留存Kafka的存储成本会非常夸张。我在压测里算过账同样一份数据放对象存储和长期放Kafka成本差距是数量级的。第二个是大规模重放的时间成本。假设你要重算最近30天的数据虽然逻辑是一套流任务但要让Flink从旧位点消费三十天的消息再全量跑一遍跑几个小时是常态。这期间还要考虑下游存储是否支持覆盖写入。如果每次改口径都要这样重放一轮迭代效率其实比Lambda跑批还低。第三个问题是复杂计算无法单纯靠回放还原。流式任务里做一个按用户维度的去重统计依赖的是状态存储比如Flink的RocksDB状态Kappa重放日志还原结果的假设是状态可以从头重建。但很多业务场景中状态里还混着外部系统的维表变化、人工补录数据这些不在日志里重放是补不回来的。3.3 什么时候选Kappa是划算的所以纯Kappa并没有真正成为行业主流更多人采用的是Kappa思想必要批处理兜底的混合形态日常实时链路统一走Flink不再维护Spark批任务只有需要全量重算或回溯历史时才临时启动批任务或重新回放。如果你是新建一个小规模系统数据量可控、重算频率低、数据源相对干净Kappa完全可以作为主力架构。它最大的价值是让团队从批流双写里解脱出来。反过来如果你已经有了大量Hive血缘、成熟的数据治理体系硬推纯Kappa的成本会高到你不愿意承受。这个账每个团队要用自己的数据规模去算没有标准答案。4. 湖仓一体用同一张表承载批、流、分析三种负载4.1 湖仓一体改写了什么表格式存储层的作用Lambda和Kappa的争论本质上是批处理引擎和流处理引擎谁说了算。但湖仓一体出现后大家突然发现更值得争论的东西其实在更底层——存储格式。传统数仓用Hive表数据以Parquet/ORC文件存储在分布式文件系统上但Hive表没有强事务能力不支持高效的增量更新流式写入很难直接落Hive表。而数据湖比如纯粹的Parquet文件外加对象存储灵活归灵活查询性能和事务保证又不够很难承担数仓的责任。湖仓一体的核心是引入了一个表格式存储层。以最常见的Apache Iceberg为例它把HDFS/对象存储上的一堆数据文件用清单文件组织成一张有ACID语义的表支持原子提交、支持快照隔离、支持时间旅行、支持增量读取。对上层计算引擎Spark、Flink、Trino等来说看到的是一张行为接近数据库表的表对底层存储来说仍然只是普通的列式文件。这一层就像在文件和计算引擎中间加了一个聪明的目录管理器解决了数据湖没事务、数仓不灵活的老大难。如果说Lambda用双链路来解决时效和准确性的矛盾湖仓一体则换了个思路不搞两套存储而是让流和批都写在同一张表上用同一套元数据来承载不同时效的访问模式。4.2 实时链路如何在湖仓一体上落地我在团队里做湖仓一体验证时落地形态大致是这样的Kafka继续作为实时消息入口Flink消费Kafka写入Iceberg表以分钟级频率提交快照下游Trino/Spark既可以基于最新快照做全量分析也可以做增量读取读取最近几分钟新增的数据原有离线任务从Hive表切到Iceberg表共享同一份数据。Flink写入Iceberg这个环节实际是为了替代Lambda里的速度层。之前实时链路是把计算结果写进Redis或哪个OLAP存储现在改为把明细数据以增量落Iceberg表计算由下游查询的时候完成或者由后续层级的Flink任务再做聚合。好处是实时结果的历史数据也能被回溯而不是只保存在一个会被定期清理的Redis里。代码层面的建表演示大概是CREATE TABLE dwd_order_detail ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10,2), status STRING, event_time TIMESTAMP ) USING iceberg PARTITIONED BY (days(event_time));Flink写入端的关键参数则通常类似这样flink.sink.commit.interval 1 min write.format.default parquet write.upsert.enabled true write.compact.small-files.enabled truecommit interval设成1分钟意味着最坏情况下实时分析能看到的数据延迟是分钟级这比Lambda速度层的毫秒级响应慢但对于绝大多数分钟级决策的业务场景来说已经完全够用。而它换来的收益是实时和离线不再各占一套存储数据只有一份。4.3 数据一致性是如何被收敛的很多团队从Lambda切到湖仓一体最直观的感受是不用解释为什么两个数不一样了。这背后是设计逻辑变了Lambda里实时结果和离线结果是两个任务独立算出来的天然有差异湖仓一体里实时链路和离线链路消费同一条明细数据只是读快照的方式不同——一个读最新快照一个读凌晨跑批时的历史快照。同一份明细同一个口径差异自然就被压到了最小。当然同一条明细不代表完全没有差异迟到数据、乱序数据仍然需要处理。但这些问题的处理方式变成了一项标准工作Flink里设置watermark规定迟到多久的数据丢弃或重算Iceberg表侧配合upsert能力让后到的修正数据能按主键覆盖旧值。相比Lambda里两套逻辑对不上不得不人肉核对这已经是方法论层面的升级了。4.4 湖仓一体解决不了的事秒级响应与小文件治理湖仓一体不是银弹这一点必须说清楚。第一个解决不了的问题就是秒级响应。分钟级快照决定了它适合做准实时但支撑不了那种真正需要毫秒/秒级查询的在线场景。这种场景还是需要单独的OLAP存储配合流式计算而不是指望直接从Iceberg表查。第二个是流式写入带来的小文件问题。Flink每隔1分钟提交一次快照一天下来会产生大量小块Parquet文件如果不做合并查询性能会严重退化。所以生产环境必须配套Compaction机制要么用Flink写端自带的小文件合并要么用Iceberg的定期元数据整理任务。我见过不止一个团队因为忽略了这件事上线两周后Iceberg表的查询从秒级退化到分钟级最后哭着排查才发现表里小文件数量已经达到几十万个。所以湖仓一体的运维并不比Lambda轻松多少只是把排查批流差异的人力转移到了治理小文件、优化提交策略、管理快照保留时间上。关键是要提前有预期别等出了问题再补课。5. 从架构选型到落地的实战清单与避坑指南5.1 选型判断的两条硬性标准当你站在要不要换架构的路口时我建议先不急着比框架、比特性而是拿两条硬性标准去卡第一你的数据资产是一套还是两套如果已经长期维护着Lambda双链路那说明你至少有两套明细、两套聚合甚至两套血缘。湖仓一体的改造本质上是把这双份资产合并成一份是一个偏资产整合的工程周期不会短。如果还没形成双链路只是实时需求刚起步那完全可以直接按湖仓一体的思路搭第一版省掉后面痛不欲生的整合过程。第二你的实时需求到底是秒级在线还是分钟级分析如果是前者湖仓一体不能解决你的核心问题你需要的是独立实时链路加OLAP存储。如果是后者湖仓一体就是很合适的底座。拿着这个标准去卡你会发现很多团队其实是被传说中的实时大屏吓到了真问下来业务说分钟级就够了。我把Lambda、Kappa和湖仓一体Lakehouse的核心差异整理成了一张对照表方便你按自己场景对号入座维度LambdaKappa湖仓一体计算引擎批流两套流为主必要时批批、流、分析共享同一存储数据存储批存储实时存储双份Kafka长留存实时存储统一表格式存储时效性秒级速度层秒级分钟级一致性难保证需要对账单一链路好一些同一份明细天然一致复杂度高中中适合场景成熟离线体系少量实时小规模/低重算频率实时离线融合、口径一致性要求高5.2 迁移落地三步走如果你决定往湖仓一体靠我给一个经过验证的三步走策略别想着一步到位第一步把Hive存量表迁移到Iceberg格式。这一步纯粹是格式切换业务SQL理论上不需要变计算引擎换成能读Iceberg的版本即可。注意先选几张大而稳定的核心表试点跑通备份、回滚、查询验证的流程再逐步铺开。第二步引入增量实时写入链路。Kafka加Flink从业务库或消息中间件同步数据到Iceberg表。这里要控制好提交间隔我建议起步设1分钟既可以保证时效又能避免太频繁导致小文件爆炸后续可以根据查询需求逐步优化到5分钟或10分钟找到一个成本和时效的平衡点。第三步收敛服务层统一读口径。把原来实时读Redis/OLAP、离线读Hive的两条读取路径统一改成读Iceberg表必要时再加一层OLAP加速但尽量避免另算一套口径。这一步做完你才算是真正从Lambda/双链路形态切换到了湖仓一体形态。5.3 最容易踩的四个坑讲几个我们在实际运行中踩过、也帮别人排查过的坑。第一个坑小文件只建不管。前面已经说了Flink频繁提交快照会疯狂产生小文件。除了开启自动合并还要建立一个定时任务监控文件数量建议每天检查一次每个表的平均文件大小低于某个阈值就手动触发compaction。宁可多跑几次合并也别等到查询卡死。第二个坑快照保留时间设置太随意。Iceberg支持时间旅行能回溯历史快照但快照元数据也是要存要管的。有些团队为了保险把快照保留时间设到超大结果元数据越积越厚多久都不清理后来每次读表都要扫描大量清单文件性能惨不忍睹。合理做法是结合数据回溯需求设置快照保留窗口比如保留7天足够超过就自动清理。第三个坑乱序数据不设置watermark和retry策略。湖仓一体虽然让数据只有一份但如果Flink任务处理乱序数据的方式不对迟到的数据要么丢要么重复依然会造成下游对不上账。一定要显式定义watermark设置upsert主键让同一条消息的修正版本能覆盖旧版本。第四个坑Schema变更没有演练。实时写入链路还在跑的时候修改Iceberg表的字段类型或分区策略很容易导致查询报错或分区错位。任何Schema变更都先在小范围试跑确认Flink写入端兼容之后再上生产。6. 展望流批一体的下一步与团队应对思路6.1 未来几年大概率会发生的三件事湖仓一体不是终点它只是当下这个阶段的最优解。站在现在看未来我判断大概率会发生这样三件事。第一表格式存储对实时更新的支持会越来越强。Iceberg、Hudi、Delta Lake这个方向的演进会很热闹谁能在流式高频更新、秒级可见性上做得更好谁就会成为下一代实时数仓的底座。现在分钟级提交未来可能变成10秒甚至秒级提交而成本增长可以控制住。第二计算引擎的路线之争会收敛。Spark和Flink会越来越像一套代码批流两用在工程上会更成熟。当流批共享的不只是存储还有计算逻辑时Lambda遗留的同逻辑两套代码问题才会真正从根上消失。第三数据服务层会变得更厚。实时的查询加速、统一的口径管理、自动化的数据质量校验会从附加品变成数仓平台的标配能力。数据架构的竞争会从谁能算得更快转向谁能管得更好。6.2 架构演进没有终点只有当前问题的最优解回看这几年的演进Lambda用复杂度换时效Kappa用重放换统一湖仓一体则用表格式存储换一致性和灵活性。每一种架构都不是完美的它们只是在特定规模、特定需求下的最优解。对我们搞数据平台的人来说最重要的不是跟上每一个新概念而是想清楚你的业务真正需要多高的时效你能接受多大的架构复杂度你的团队有多少精力去维护底层组件把这些问题的答案统一起来架构的形态自己会浮出水面。最后分享一个我的心得选型时不要只看各种框架的官方文档和社区文章新架构好不好要拿自己真实的数据规模去压测特别关注小文件、并发写入、快照清理这些文档里不写但生产一定会遇到的细节。至少跑两周让新架构处理过真实的脏数据、数据回补、组件宕机你才有资格说它行不行。
阅读完成 · 觉得有帮助?