我印象很深的一次需求评审业务方提出数据看板要“秒级”更新说是活动效果不对就没办法及时调价还反问了一句“你们不是有大数据的平台吗怎么连实时数据都出不来”当时我们用的还是T1的离线数仓凌晨跑批早上九点出报表。会议结束后我拉着团队讨论了很久结论很现实——没有一套实时数据仓库这个需求根本接不下来。这篇文章我会尽量不写空话把实时数据仓库这几年的发展脉络、主流架构思路、落地时容易踩进去的坑以及我对未来方向的判断一次讲透适合正在做或者准备做实时链路的数据工程师、架构师和技术决策者参考。1. 实时数仓的火不是偶然T1架构的三大死穴先别急着聊架构和选型我们得先回答一个问题为什么实时数据仓库这几年集中爆发很多人以为是技术厂商炒作其实是传统离线架构在业务侧积压了好几轮不满加上技术侧恰好成熟到能够接住这些需求。两边同时到位才让实时数仓从一个“锦上添花”的词汇变成了很多团队的必选项。1.1 业务侧等不起的五分钟与部门级浩劫T1离线数仓最大的问题不是慢而是它的“慢”会传导成业务决策的滞后。我见过不少业务场景其实真正需要的延迟窗口只有几分钟比如大促期间实时库存扣减、营销活动效果实时反馈、出价系统的实时调价、物流车辆轨迹实时监控。这些场景如果用离线批处理去支撑要么只能看昨天甚至上周的数据要么干脆得写一堆临时脚本轮询数据库结果把生产库拖到告警。更让数据团队难受的是业务方并不会因为“技术做不到”就放弃需求。他们会用各种方式证明自己需要实时数据比如把离线看板的更新时间调成每五分钟刷新一次然后在群里你说“数据不对和订单库的数字对不齐”。这就是部门级浩劫的开始——你没有实时链路就只能让业务在错误的时间窗口里做判断。最典型的是零售和电商行业的大促场景。零点开卖头十分钟订单量就能冲得很高如果运营只能看到前一天的销售汇总那基本上等于闭着眼睛开车。等到离线报表跑出来活动已经结束了该补的货没补该调的价格没调。业务侧对数据团队的信任因此逐步下降这是推动实时数仓落地的最原始动力。1.2 技术侧刚好到齐的三块拼图单有需求还不行得有三块技术拼图同时成熟第一是流式计算引擎。早期做实时计算主要靠自研或者Storm编程门槛高、状态管理弱窗口和乱序处理更是要靠人肉保证。Flink全面流行之后事件时间、Watermark、状态后端、端到端Exactly-Once这些能力成为标配实时数据仓库的“计算大脑”才算真正可用而且Flink SQL把开发门槛大幅拉低普通数据工程师也能上手。第二是消息队列的可靠性与留存能力。Kafka这类分布式消息系统做到高吞吐、持久化、多副本数据在消息队列里可以保留几天甚至更久这为“数据回放”和“多消费者复用同一份实时数据”提供了基础。没有这一步实时链路里的数据就无法被多个下游安全共享实时数仓也就无从谈起。第三是OLAP存储引擎和列式存储的爆发。ClickHouse这类列式数据库在千万级数据量下的聚合速度可以达到秒级甚至毫秒级支撑“明细数据已入库即可查”的场景而Iceberg、Hudi这类数据湖表格式又把流式写入和批量写入统一到同一张表上让实时数据和离线数据最终能落到同一套存储底座里。这三块拼图都到位之后实时数仓才从“能跑通Demo”变成了“能上生产”。我用一个简单类比流式计算是流水线上的工人消息队列是传送带OLAP存储是成品仓库。传送带和仓库一直都有但以前工人技术不行现在工人终于能看懂图纸、控制节奏了。2. Lambda、Kappa和“第三条路”实时数仓架构演进复盘聊实时数仓绕不开两套经典架构一个是Lambda一个是Kappa。这两套思路之争几乎贯穿了实时数仓的整个发展史。真正落过地的人都知道架构选型不是非黑即白的选择题而是基于团队能力、数据规模和运维成本的判断题。2.1 Lambda架构的工程代价Lambda架构的思路最简单业务数据同时进入两条链路一条是批处理链路负责全量、准确的历史计算另一条是流处理链路负责增量、低延迟的实时计算。最终查询层把两条链路的结果合并输出。听上去很完美——准确性和实时性兼得。但落地之后你会发现问题一个接一个。最大的问题是同一套业务逻辑要写两遍。批处理里用Spark写一套指标逻辑流处理里再用Flink写一套同样的指标逻辑两套代码维护各自的版本经常出现批任务的指标口径改了、流任务漏改或者两边的时间处理方式细微不一致最终导致同一份数据在离线报表和实时看板上对不上。每次业务方拿实时和离线数字对比数据团队都得花大量时间去排查口径差异这套架构的运维成本会迅速超过收益。我见过一个团队用Lambda架构做了半年最终实时层和离线层的结果偏差始终收敛不了负责人甚至专门设了一个“口径对齐会”每周开一次专门讨论“为什么实时GMV比离线GMV多了一千万”。这种会议开几个月团队士气基本就没了。当然Lambda并非一无是处它的优点是思路清晰任何团队都能在短时间内搭起来。如果你的实时需求只有一两个指标并且团队没有人能驾驭纯流式架构那Lambda还是可以作为过渡方案。但请把“离线链路最终修正实时结果”这件事明确写进设计文档否则后面就是长期债权。2.2 Kappa架构的取舍Kappa架构的出现就是为了解决Lambda的“双份代码”问题。核心思想是所有数据统一走流式链路用消息队列做数据缓存一旦需要重新计算历史指标就重新读取消息队列做一次流式重放。这套思路在理念上更简洁也符合很多人对“实时数仓应该只有一条链路”的直觉。但Kappa落地有两个绕不过去的坎第一个是消息队列的保留窗口问题。Kafka虽然可以长时间留存数据但留存成本和查询能力都有限。做全量历史重放时如果数据量很大你要么需要把消息队列的保留周期拉得很长要么就得先做一次全量数据灌入再把它当成一条流重新跑。实际操作中很多团队发现“全量重放”根本不是一个愉快的体验——消息积压会打爆下游重放期间的状态管理稍有不慎就会产出重复或者丢失的数据。第二个是批量语义的缺失。流处理引擎虽然越来越强但在一些需要“一次性扫描全量数据”的复杂计算场景里效率和可表达性还是不如批量处理。业务上经常出现“我想修正上个月的某个指标重新按新口径算一遍”这种需求这对Kappa来说等于重跑一遍全量流无论从计算成本还是从数据时效性上看都不划算。所以我的判断是纯Kappa适合数据规模可控、指标口径变化不频繁、回刷需求少的团队。如果你在一个业务变化极快、口径每个月都改的互联网公司纯Kappa会让你在“重新计算历史”这件事上痛苦不堪。2.3 流批一体和湖仓一体当前更现实的折中路线那么Lambda和Kappa到底怎么选我的经验是别急着二选一看看第三条路——以流为主、保留批量能力存储层流批统一。这是目前大多数团队实际采用的做法也是我认为最务实的架构形态。这套架构的特点是实时计算链路负责日常的低延迟指标输出离线批处理链路负责每日的全量基线校正。两者共用一套表结构定义和指标口径数据最终落到同一套支持流批写入的存储格式里例如基于数据湖上构建的统一表格式。在线查询时实时数据优先响应但一旦实时链路有异常或者指标可疑可以立刻被离线基线替换或覆盖。这样做的好处很明显。第一代码不会写两遍因为指标口径可以定义在统一的语义层里流批任务共享同一套UDF或SQL模板。第二存储层是统一的数据读取方不需要关心数据是来自实时还是离线减少了查询层的复杂度。第三回刷时可以用批量能力快速修正历史而不是依赖消息队列重放整个流。我用一个生活化类比来解释这套折中路线实时链路是前台接待员能快速回答大多数问题离线链路是档案管理员每天夜里把全天的资料重新整理归档。前台回答不了或者回答错的问题档案管理员第二天早上来修正。查询的人不需要知道答案到底是谁提供的只要看到一致的版本。这套架构下实时链路产生的数据质量很大程度上决定了系统的可用性所以本文后面会用一整章来聊实时数仓落地时的坑。在进入细节之前先看一套完整的、可落地的分层设计。3. 一套可落地的实时数仓分层从采集到服务的组件选型实时数仓不是把离线数仓的表结构照搬一遍就完事而是要在保证低延迟的同时尽量复用离线数仓的分层思想让数据血缘清晰、指标可解释、任务可运维。下面这套分层方案是基于我自己的落地经验总结出来的不依赖具体厂商任何团队都可以照着评估和调整。3.1 实时分层模型对齐离线数仓的通用体系实时数仓的分层大体可以复用离线数仓的经典四层模型只不过每一层的载体和实现手段发生了变化分层离线数仓的典型载体实时数仓的典型载体核心职责ODS操作数据层HDFS/离线表Kafka Topic原样接入业务数据、CDC日志不做过多的清洗DWD明细数据层数仓宽表层Flink SQL处理后写回Kafka或数据湖清洗、过滤、维度补充、流流Join、形成明细事实DWS汇总数据层离线汇总表Flink的窗口聚合结果写入OLAP按维度预聚合秒级/分钟级指标ADS应用数据层报表/结果表OLAP查询加速、Redis缓存直接服务大屏、指标API、实时看板为什么还要坚持分层因为分层本质上是在每一层都做一次“收敛”让下游可以用尽量少的代价取得稳定的数据。实时数仓如果不分层每个业务方都直接消费最底层的CDC原始数据那么连接数、计算复杂度、数据口径都会迅速失控。分层之后才能把“复杂计算”和“简单查询”解耦开。3.2 各层核心组件的选型对比选型这件事永远没有标准答案但我可以给你一张我们实际用下来觉得合理对比表帮你减少试错成本。组件层候选方案适用场景注意事项数据接入CDCFlink CDC、Debezium、Canal业务库变更实时捕获CDC对源库有一定压力生产环境要注意源库的binlog保留期和资源占用消息队列Kafka、Pulsar实时数据的缓冲、分发、回放关注消息保留期和Topic分区数规划分区数远大于消费者并发度会造成浪费流式计算Flink、Spark Structured Streaming实时清洗、Join、聚合、状态管理Flink在窗口与状态管理方面更强Spark Streaming更适合与既有Spark生态衔接明细存储Kafka多Topic、HBase、Apache Iceberg/Hudi明细数据的查询和回放明细查询量小时Kafka即可需要随机查明细用HBase需要流批统一读写选Iceberg/Hudi汇总/指标存储ClickHouse、Apache Doris、DruidDWS/ADS层的在线聚合和即席查询关注分片键设计、前缀索引、数据模型选择尽量避免大宽表的随意JOIN服务加速Redis、本地缓存、指标服务网关面向外部接口的低延迟返回实时数仓的指标API要注意限流和降级避免OLAP被高频大查询压垮单看表格还不够我挑两个最容易被选型坑到的点展开说。首先是明细数据放哪。很多团队刚做实时数仓时习惯把所有明细都堆在OLAP存储里比如直接写入ClickHouse然后让业务随便查。如果明细量在百万级以内还能撑住一旦到了亿级以上OLAP的存储成本、写入压力、查询隔离都会出问题。更合理的做法是明细层写Kafka多主题或者数据湖只有预聚合过的汇总指标才进OLAP存储。需要查明细时按事件ID或订单号去HBase/数据湖里点查而不是每次都扫全表。其次是指标计算放哪。实时数仓的核心能力是实时聚合但Flink里做聚合和ClickHouse里做聚合要有所取舍。Flink适合做状态型聚合比如累计UV、实时会话ClickHouse更适合无状态的大规模聚合查询比如任意维度的交叉分析。最理想的分工是Flink把能够预计算的指标先算好ClickHouse只负责对剩下的偶发分析做即时查询而不是一面让Flink疯狂输出原始明细一面让ClickHouse做全量group by。3.3 维度关联与“真实时”的代价实时数仓里最容易被忽略的是维度表。离线数仓做维度关联很简单直接JOIN一张维度表即可实时数仓却要面对“维度值会变”的现实——用户改了地区、商品换类目、门店调整归属这些变更会实时发生而下游可能只有实时流。我们的做法是把维表加载到Flink的缓存里配合变更流做异步刷新。具体来说就是维表以变更日志形式同步到一个长期有效的存储中Flink通过异步IO查询并缓存收到变更消息后主动更新缓存而不是等待缓存过期。否则你看到的实时明细里维度永远是第一次加载时的旧值。还要有一个思想准备真正意义上的“实时”是要付出计算成本的。状态后端存储全量维度和累计状态每多一个维度组合状态量就可能翻倍。所以我的原则是能预先JOIN的不要运行时JOIN能在离线预生成的维表不要实时动态更新只有那些必须实时变化的维度才值得进入实时链路。4. 实时数仓落地时避不开的隐形坑清单这一部分才是本文最值钱的地方。网上讲架构的文章很多但真正把实时数仓跑上生产你一定会撞到下面这些坑。每一个都是我反复排查、加班应急换来的经验写出来供大家参考。4.1 Exactly-Once不是魔术幂等设计才是兜底很多团队听到Flink支持端到端Exactly-Once就觉得数据绝对不重不漏这是最大的误解。严格来说Flink两阶段提交可以保证“算子级别不重复处理”但下游存储如果不配合事务或者幂等键最终效果照样可能重复。举个例子实时汇总结果写入OLAP时如果使用普通INSERT一旦作业发生故障恢复后重放部分窗口你就有可能出现重复的汇总记录。过一会儿你会发现实时大屏上的成交金额突然多了几十万而你根本说不清是业务真的涨了还是数据重放导致的。正确解法有两个维度。一个是Flink侧开启两阶段提交但这要求下游Sink支持事务比如Kafka和事务型数据库可以ClickHouse这类分析型数据库往往支不支持或不稳定。更普适的方案是让写入操作本身幂等在写入端定义唯一键使用幂等更新如类似“唯一键版本号去重”的写入模式或者给每一条实时数据加一个全局业务唯一ID在下游去重。我最后的结论是不要依赖单一机制做好“源头唯一ID 计算幂等 存储去重”三层防线。再Flink的Exactly-Once再强也要当作“最后一层没守住”来设计你的存储写入逻辑。4.2 状态管理失控从OOM到Checkpoint失败Flink的状态是实时计算的“记忆”但这种记忆也是真实的内存和磁盘消耗。大状态的典型场景是按用户粒度的累计行为、长时间窗口的会话聚合、维度表缓存特别大。状态一旦膨胀轻则Checkpoint超时重则节点OOM崩溃。我遇到过一次相当狼狈的事故一个累计一天的UV作业状态后端用的是内存态到了下午Checkpoint越来越大最后作业只是反复做Checkpoint数据处理延迟从秒级变成了小时级。后来全部迁移到基于文件系统的状态后端给每个算子设置了合理的TTL才把状态量按住。所以实时数仓的作业设计要在一开始就想清楚这几个问题这个算子需要保留多长时间的状态这些状态能不能用增量聚合替代全量明细老的Key什么时候可以淘汰给状态设置合理的TTL以及在聚合中使用ReducingState而不是ListState只存聚合结果是两条最立竿见影的优化手段。还有一点大状态作业做扩容或者修改并行度时一定要考虑状态迁移和重均衡的成本否则改一个并行度就要停几小时。4.3 乱序、延迟与迟到数据别用处理时间掩盖问题实时数据最本质上区别于离线数据处理的地方就在于“你永远无法确定某一刻的数据是否已经到齐”。上游业务库的binlog传输有延迟网络抖动甚至业务系统自己也会有异步推送这些都会导致事件乱序。不少团队最初为了省事直接默认使用处理时间ProcessTime来做窗口也就是说“数据什么时候到就算什么时候的”。这样做在低延迟内部链路里可能暂时没问题但只要上游一抖动实时指标就会产生毛刺或者异常波动——因为昨天下午三点的订单可能在今天下午才到达你的窗口算完就被归到错误的统计区间了。正确的做法是事件时间EventTime配合Watermark与AllowedLateness。你需要设置一个合理的迟到容忍时间比如五秒或十秒Watermark只推动窗口计算触发但迟到数据在容忍范围内还能进入累计结果。超出容忍上限的数据则从侧输出流接到专门的补偿链路宁可延迟修正也不能让它丢失。很多人会设置一个覆盖所有故障场景的超大Watermark延迟比如十分钟但这样做的代价是实时指标也会延迟十分钟输出。我平时会先看P95的数据到达延迟再结合业务容忍度设置一个平衡值比如P95延迟两秒我就设三到五秒的Watermark延迟而不是无脑设大。4.4 写入存储的小文件与实时链路的分区管理实时任务一直小批量写数据湖或文件系统会持续产生大量小文件。问题可能在写入后的第三四天集中爆发查询效率暴跌、元数据服务开始抖动、HDFSNameNode告警而且离线批任务扫描全表时会因为这些小文件多出成百上千倍的InputSplit整个集群都被拖慢。这里我的实践建议是能批量写就不要逐条写能做异步攒批就不要同步刷盘同时在写入表格式尾部配置一个自动合并小文件的机制定期把小于阈值的小文件合并成合适大小的文件。另一步是合理规划分区分桶比如按天分区再按某个大维度分桶让每个分区内的文件数量可控。同时联想起一条Kafka的经验Topic分区数的设计也要提前规划。很多团队默认分区数越多吞吐越大结果消费者端并行度跟不上消息积压越来越多而分区数是不能随便改少的因为会导致消费顺序变化。我的经验是按消费者的最大并行度和峰值流量来做分区数预估不要盲目追求“多”。4.5 回溯修正实时数仓最容易被低估的运维动作实时链路跑久了一定会遇到“重新计算历史”的需求业务口径变了、上游修复了历史数据、Bug导致某些时段的数据算错了。前面讲Kappa时我提到过全量重放很痛苦这里我想再展开一下因为这是实时数仓落地后最频繁也最容易翻车的运维动作。最简单的回溯思路是把Kafka的消费位点重置到目标时间点然后让流任务重新消费并重新计算。但这里有一个隐藏问题如果下游是普通存储中间结果在回溯过程中会产生临时的不一致用户会实时看到“数据先少了、再恢复”的状态。更麻烦的是状态后端里的旧状态不会自动清零如果你按天维度做累计指标但不清理昨日状态回溯结果就会被新旧数据叠加污染。我们的回溯方案一般按三步走提前在存储层把受影响的时间分区标记为“重建中”查询侧暂时切到离线副本或者直接不展示用一个新的作业或任务实例从目标位点开始消费写入一个临时结果集校验临时结果集与离线基线对齐后再原子化切换结果表或者更新分区数据。整个过程尽量模拟离线批处理的可控性而不是直接在线上流任务里做热修。数据质量的底线不能只靠实时任务自身保证实时数仓的运维体系里必须包含“临时下线、重算、校验、切换”这样的标准化流程才能让人睡得着觉。5. 趋势判断湖仓一体、数据服务化与实时数仓的未来形态章节写到这里实时数仓的架构和落地其实已经讲得比较透了。最后一章我想聊一聊对未来的判断。有一点我很确定实时数仓不会消失但它会变成一个更大的体系里面的一部分而不是一个孤立的“实时层”。5.1 湖仓一体正在吃掉部分实时数仓的“边界”“离线用数据湖、实时用消息队列OLAP、两套互不相通”的做法已经越来越不现实了。湖仓一体用统一表格式如Iceberg、Hudi这类开源方案同时支持流式写入和批量写入让同一张表既有实时数据不断追加又有批量任务定时整理。这意味着很多团队可以把实时和离线的计算输出放到同一套数据底座上消除两套存储的割裂。看起来湖仓一体似乎会“替代”实时数仓但我个人认为更准确的说法是湖仓一体会替代实时数仓的存储层而实时计算与实时服务层依然独立存在。换句话说数据湖负责解决“全量、可靠、可回流”的问题实时计算引擎负责解决“低延迟、有状态、连续计算”的问题两者在文件格式层面握手而不是在架构制高点上互相取代。架构演进的方向必然是“合并”而不是“叠加”如果你的团队现在还在同时维护离线Hive表和一套实时Kafka链路并且为它们之间的一致性吵得不可开交那么上面这种湖仓融合的存储底座值得认真调研。5.2 从“实时数仓”升级为“实时数据服务”再过几年你会越来越少听到有人强调“数仓”这个概念更多会是“指标平台”“实时数据服务”“数据API”这类说法。背后的逻辑是业务方并不关心你的数据是放在Kafka还是ClickHouse里他们只想知道“现在有多少订单”“转化率是多少”“哪些区域的库存不足”。实时数仓的价值核心不是建了一套实时表而是构建了一个能被业务方随时调用的实时数据服务。所以新架构里实时数仓的前端会越来越多出现语义层和指标服务层把指标口径统一沉淀成可复用的指标注册表通过一个标准的API网关对外提供查询能力让下游业务方订阅或者调用实时指标而不是直接发SQL给数据分析师。数据团队的工作重心从“把实时表做出来”转变为“把指标服务治理好”权限、缓存、配额、SLA这些工程能力反而成了核心竞争力。我个人建议团队设计实时数仓时从一开始就给自己设定一个长远目标把ADS层做成一套独立的、可对外提供统一接口的实时指标中间件。未来即使底层组件换了一轮业务方也不再会受到直接影响这个抽象的稳定层就是实时数仓真正的资产。5.3 智能运维与自动化校正会成为标配随着实时链路规模变大纯靠人工盯作业和修数据的模式会走到尽头。Flink作业的Checkpoint失败、数据积压、存储的慢查询这些都需要自动化的处理手段。未来的实时数仓会把实时计算引擎、消息队列、OLAP存储的监控数据统一汇入一个异常发现系统通过异常检测模型去发现“指标曲线突变”到底是因为业务变化还是链路故障并联动执行恢复动作——比如自动扩容、自动限流、自动切备份。这是AI与实时数仓结合最实际的方向而不是天天喊着用大模型写SQL。一个能够自动识别水位异常并给出处理建议的运维助手对生产环境的价值远大于一百个Demo级别的智能问答。不过我也要提醒一句自动化校正的前提是数据血缘和质量基线足够清晰。如果你的实时数仓连最基础的指标血缘和任务依赖都没有梳理清楚那么任何自动化工具进来都只会放大混乱。我见过不少团队先去买智能运维工具回头发现连“哪些任务产生了这张实时表”都答不上来。先把血缘、指标字典、质量监控这三件事做扎实再谈智能化。最后分享一点个人体会算不上总结只是踩过太多次坑之后的真心话实时数仓从来不是一个“搭完就结束”的项目它的上线只是运维的开始。不要指望一套架构能一劳永逸地解决所有问题更不要为了跟风上实时而把简单需求复杂化——如果你的业务真的只需要T1那就老老实实把离线数仓做好。只有当你遇到业务侧实实在在扛不住延迟带来的损失时再认真投入实时数仓的建设和治理那才是真正值得的投入。
阅读完成 · 觉得有帮助?