想象你经营一家生意火爆的餐厅。客人到了,既要吃得上刚出锅的热菜,又希望味道像老主厨慢炖三小时的那般讲究。怎么办?你开了两个厨房:主厨厨房负责把全部食材炖成一锅完美底汤,慢但准;快炒窗口负责客人一进门就能先端上临时小菜,快但不全。服务员则把两边出锅的菜品拼盘上桌,顾客永远只看到一份完整的美味。这套“双厨房并行、服务层拼盘”的运营思路,就是大数据领域著名的 Lambda 流批一体架构——用批处理层保证最终正确,用速度层弥补实时空缺,再用服务层把两者合并成统一查询结果,同时解决“算得准”和“查得快”两个难题。

岗位关联

理解 Lambda 架构,是成为大数据平台或实时数仓架构师的必修课。搞懂它,你就能在设计“既要历史全量准确、又要实时响应”的系统时,游刃有余地选择分层方案,并用同一套心智模型评估 Kappa 等替代架构。

目录
  1. 可视化演示
  2. 架构背景
  3. 批处理层
  4. 速度层
  5. 服务层
  6. 一致性保证
  7. Kappa对比
  8. 技术选型
  9. 应用场景
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示Lambda流批一体架构设计的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Lambda流批一体架构设计:Lamb Lambda架构整体设计与三层 批处理层与不可变数据存储 核心机制可视化 Lambda流批一体架构设计整体架构与关键组件

架构背景

餐厅开业前,你只有一口大炖锅。每天深夜才开始煮第二天的汤,客人中午才能喝上——这就是传统的批处理系统:数据攒到一批,晚上统一算,次日出报表。订单高峰时,客人问“现在有多少桌在等位”,你只能抱歉地说“等收银台下班统计”。这种模式准确却迟钝,完全没法应对“此刻必须知道”的实时请求。

于是你决定拆出两个厨房。一口锅继续慢炖,保证每天最终的账目精确无误;再开一个快炒窗口,厨房里有什么食材,三分钟就炒出一盘应急菜,先安抚客人。大数据领域也一样:批处理引擎擅长把全量历史数据算得明明白白,流处理引擎则擅长对新到的数据进行秒级加工。两个管道各司其职,互不干扰。

Lambda 架构正是把这两条管道并行铺开:同一份数据进两份工,一份走“全量大计算”,一份走“实时小快跑”。两条管道的产出不是互相替代,而是最终要在服务层汇合,就像两路出餐最后都要经过服务员的手,才能变成客人的一顿饭。

想到一套架构能把“慢慢算准”和“赶紧算出”都兼顾,就像你的餐厅终于同时拥有炖菜的深度和快餐的速度。这正是 Lambda 架构存在的意义:两条管道并行,谁也别想替代谁。

result=merge(Vb,Vs)\text{result} = \mathrm{merge}(V_b, V_s)
查询结果 = 把批处理层的全量视图和速度层的增量视图合并起来。单独看任何一边都不完整,拼起来才是正确答案。
  1. V_b(批处理视图)批处理层对全量历史数据计算出的结果,准确但更新慢,相当于炖了三小时的底汤。
    Vb=compute(Dall)V_b = \mathrm{compute}(D_{\mathrm{all}})
  2. V_s(速度层视图)流处理引擎对最近新到数据算出的临时结果,快但可能不完整,相当于应急小炒。
    Vs=compute(Drecent)V_s = \mathrm{compute}(D_{\mathrm{recent}})
  3. merge(合并)服务层把两路结果按查询键拼装成最终答案,客人只知道自己吃到了一顿完整饭,不知道背后有两条出餐线。
    result=merge(Vb,Vs)\text{result} = \mathrm{merge}(V_b, V_s)

批处理层

主厨厨房的规矩是“全部食材一次性下锅”。不管今天来了多少客人,晚餐的汤底必须从开业第一天起所有的排骨、萝卜、药材一起炖——多一块少一块都不行。批处理层的哲学正是如此:每次计算,都对全量历史数据重新跑一遍。

这样做的好处是什么?准确性。因为数据是完整集,计算结果是全盘真理。哪怕昨天的代码出了 bug,修复后重跑一遍,就能得到完全正确的答案。这个特性来自一个核心设计:数据不可变。主数据日志只追加、不修改,就像餐厅从不扔掉菜谱记录,而是不断往上写新条目。历史永远可回溯、可复现,任何时候想重算一次都是安全的。

代价也显而易见:全量重算很慢。数据量越大,耗时越长,通常是小时级甚至天级。所以批处理层根本不要试图追赶实时,它的定位是“迟到的正确”。在 Lambda 架构里,批处理层的产物被称为批视图(Batch View),它是最终一致性的权威来源。

如果把服务层比作服务员,那批视图就是后厨那本厚厚的标准菜谱。每道菜理论上该怎么出品,菜谱说了算。实时再乱,最终校准还是要回到这本菜谱上。

Vb=compute(Dall)V_b = \mathrm{compute}(D_{\mathrm{all}})
批视图 = 对全部不可变历史数据执行全量计算。公式很朴素,却是整个架构的“正确性压舱石”。
  1. D_{\mathrm{all}}(全量数据)所有历史数据组成的不可变主数据集,只增不改,相当于从开业第一天存到现在的全部采购记录。
    Dall={d1,d2,…,dn}D_{\mathrm{all}} = \{d_1, d_2, \ldots, d_n\}
  2. \mathrm{compute}(批处理计算)用离线计算框架对全量数据做完整聚合,允许花费数小时,但必须精确。
    compute:Dall↦B\mathrm{compute}: D_{\mathrm{all}} \mapsto B
  3. V_b(批视图)计算后的结果集落库供查询,它是最终答案的基准版本。
    Vb=BV_b = B

速度层

快炒窗口的定位很明确:在炖菜出锅之前,先给客人端上一盘能填肚子的菜。它只使用最近几分钟到账的食材,翻炒两下就上桌,味道可能差些火候,但至少让客人不用干等。速度层(Speed Layer)正是在批处理层“补课”之前,用流式引擎处理新到的增量数据,产出准实时视图。

速度层算的是什么?是批处理周期内新产生的数据。例如批处理每天跑一次,速度层就负责处理“从上次批处理结束到现在”这段时间的数据。它只为查询提供临时补丁,并不追求完美。偶尔因为窗口不完整导致多算少算,完全可接受——反正最终会有批处理的全量结果来兜底。

代价是牺牲了一部分精确性,换来了秒级甚至毫秒级的响应速度。这也意味着速度层和批处理层的产物天然存在“双份代码、双份运维”的成本。你要维护两套计算逻辑,就像同一道菜要养两个厨师团队来分别做慢版和快版。这是 Lambda 架构最知名的痛点,也是后来 Kappa 架构试图解决的矛头所在。

速度层的存在意义,就像快炒窗口让客人坐下第一分钟就能有菜入口。它不完美,但它的“不完美”恰恰是整套系统能实时响应的关键——用暂时的粗糙,换来即刻的可用。

Vs=compute(Drecent)V_s = \mathrm{compute}(D_{\mathrm{recent}})
速度视图 = 对最近一个时间窗口的增量数据做流式计算。速度层不求准确,只求此刻能看到数。
  1. D_{\mathrm{recent}}(增量窗口数据)批处理尚未覆盖的新到数据,例如最近 10 分钟的订单流。
    Drecent=Dall∖DcoveredD_{\mathrm{recent}} = D_{\mathrm{all}} \setminus D_{\mathrm{covered}}
  2. \mathrm{compute}(流式计算)用 Flink、Storm 等流引擎做持续增量计算,每来一条记录就更新一次结果。
    compute:Drecent↦S\mathrm{compute}: D_{\mathrm{recent}} \mapsto S
  3. V_s(速度视图)临时存储的补丁结果,等批处理追平后会被清空。
    Vs=SV_s = S

服务层

服务员是客人唯一打交道的角色。他不需要告诉客人“您这是炖菜配的快炒小菜”,他只需要把两路菜拼在一只盘子里端上来。服务层(Serving Layer)干的就是这件事:对外提供统一的查询能力,对内合并批视图和速度视图。

具体来说,服务层会把批视图预计算好、构建索引,存放在 HBase 一类支持低延迟查询的存储中;同时把速度层的近期补丁也纳入查询范围。当用户问“某个指标现在是多少”,服务层会先取批处理部分的准确结果,再叠加速度层的增量补丁,合成最终答案。整个过程对用户透明,用户看到的始终是一个完整响应。

这个合并动作也是 Lambda 架构“最终一致性”的落地方式:批处理结果没到位时,答案含有速度层补丁;等批处理追平,速度层补丁清空,答案就完全以批视图为准。从外面看,系统无论何时都能回答问题,只是答案的“精确度”随时间推移不断提升,直到最终完全一致。

服务层把两套厨房的烟火气化成一盘从容的菜。对顾客而言,没有“两条线”,只有一份答案;对系统而言,没有“两个结果”,只是一个不断趋向精确的查询响应。

answer=Vb(x) ⊕ Vs(x)\text{answer} = V_b(x) \, \oplus \, V_s(x)
最终答案 = 批视图中的结果叠加速度层补丁。两个输入一个都不能少,服务层负责把它们拼成一件完整的成品。
  1. V_b(x)(批视图子集)从批处理结果中取出与查询 x 对应的部分,这是准确基线。
    Vb(x)⊆VbV_b(x) \subseteq V_b
  2. V_s(x)(速度层补丁)取出速度层中尚未包含在批视图里的新数据结果,临时填补空白。
    Vs(x)⊆VsV_s(x) \subseteq V_s
  3. \oplus(合并叠加)将两个子视图相加或合并,输出最终查询结果。两个数分别可能为零,但不能同时缺失。
    answer=Vb(x)+Vs(x)\text{answer} = V_b(x) + V_s(x)

一致性保证

你可能会担心:炖菜和小炒口味能对上吗?开始时当然对不上——小炒用的急火,炖菜用的小火,味道必然有差异。但只要炖菜真正出锅,以菜谱为基准的小炒就会被替换掉,最终全店出品味道统一。Lambda 架构的“最终一致性”正是这个道理:系统在任意时刻都可用,但答案的精确性最终由批处理层说了算。

机制上,速度层的数据会随着批处理层每轮完成而逐步清空。假设批处理每 6 小时跑一次,那么从上次批处理结束到下一次完成之前,速度层始终背负着“补齐缺口”的使命。一旦新的批视图落库,服务层立刻切换到以批视图为主的权威答案,同时把速度层对应的补丁区间删除,避免造成重复计算。

这种设计带来一个特性:任意时刻查询都有结果,但结果精度随批处理周期呈锯齿状收敛。批处理刚完成时最精确,随后新数据涌入,速度层补丁再次顶上,精确度暂时回落,如此往复。理解这个节奏,你才能在生产环境中给用户合理的预期:不要指望实时指标在下一秒就与最终报表完全一致,但可以保证“最终一定一致”。

最终一致不是“永远不一致”,而是“先凑合、后补齐”。就像餐厅打烊时对照菜谱一页页复盘,确认今天每道菜最终都对得上标准——客人等得起,数据也算得清。

lim⁡t→∞Vs(t)=∅\lim_{t \to \infty} V_s(t) = \emptyset
时间足够长、批处理不断追平之后,速度层补丁会趋近于空集——最终一致性达成。
  1. \lim_{t \to \infty}(极限过程)随着时间推移,每一轮批处理都会收敛更多的数据区间。
    lim⁡t→∞\lim_{t \to \infty}
  2. V_s(t)(随时间变化的速度视图)速度层补丁的体量随着批处理完成而被清理,动态减小。
    Vs(t)→shrinkingV_s(t) \to \mathrm{shrinking}
  3. \emptyset(空集)当批处理覆盖了全部数据,速度层完全没有存在的必要,补丁归零。
    Vs(最终)=∅V_s(\text{最终}) = \emptyset

Kappa对比

如果双厨房让你觉得养两班厨师太贵,有人提出了一个精简方案:只留快炒厨房,把所有历史菜谱全部倒进锅里,用同一种做法从头到尾重做一遍。这就是 Kappa 架构:用一套流处理引擎,通过重放日志,完成原来批处理层的全部工作。它不需要专门的批处理层,自然也没有“双份代码、双份运维”的烦恼。

Kappa 的巧妙之处在于,流处理引擎理论上可以消费 Kafka 里保留的全部历史消息,重算整个数据流。这样既能做到 Lambda 的准确性(因为全量日志都在),又只维护一套计算逻辑。听起来完美,但代价藏在细节里:重放超长历史日志非常耗时,而且流引擎的状态管理、窗口计算在面对历史级数据时未必比批处理更高效。

所以选型本质是一场取舍。如果团队技术栈以流为主、历史数据量适中、重放成本可控,Kappa 的简洁极具吸引力。如果历史数据庞大到重放一次要跑好几天,或者业务明确要求随时对任意历史区间做精确重算,那么 Lambda 的批处理层依然不可替代。没有银弹,只有“适合你的场景”的方案。

对比下来,选 Lambda 还是 Kappa,就像决定要不要保留炖菜厨房:客人多、历史长、重视精确,那就忍着成本养双厨房;客人急、历史短、重速度,单厨房也许就够。

Vkappa=stream(replay(Dlog))V_{\text{kappa}} = \mathrm{stream}(\mathrm{replay}(D_{\mathrm{log}}))
Kappa 的视图 = 用流计算引擎重放全量日志数据得到的结果。一条管道打天下,但重放成本是隐形代价。
  1. D_{\mathrm{log}}(日志全量)Kafka 等消息队列中保留的全部历史数据,是重放的原料。
    Dlog=所有已发生的消息D_{\mathrm{log}} = \text{所有已发生的消息}
  2. \mathrm{replay}(重放)从最早的消息开始,按顺序重新投递到流引擎,模拟一次完整计算过程。
    replay:Dlog↦数据流\mathrm{replay}: D_{\mathrm{log}} \mapsto \text{数据流}
  3. \mathrm{stream}(流式处理)流引擎在重放过程中对全量数据进行处理,最终产出和批处理等价的视图。
    Vkappa=stream(重放出来的数据流)V_{\text{kappa}} = \mathrm{stream}(\text{重放出来的数据流})

技术选型

再回到餐厅:有了双厨房的规划,你还需要具体买什么锅、什么灶。Lambda 架构的每层都有成熟工具可选,架构师的工作就是给每个厨房挑趁手的家伙。批处理层用 Hadoop MapReduce 或 Spark 做全量计算,HDFS 存不可变的主数据日志;速度层用 Flink、Storm 或 Kafka Streams 做流式计算;消息中间件首选 Kafka,负责数据接入与缓冲;服务层用 HBase、Cassandra 这类支持随机低延迟查询的列式数据库存批视图。

选型时最常听到的一句话是“批处理与流处理一体化”,但 Lambda 架构本质上并不要求同一套代码。恰恰相反,它默认你会在两个层用不同的技术栈——批处理追求吞吐,流处理追求时延。Kafka 在这里扮演“总食材仓库”的角色:所有原始数据先进 Kafka,再分别拉给批处理层和速度层。这样即使某一层挂了,数据也不会丢,另一条管道依然能独立运转。

服务层的存储选择也需要讲究。因为批视图要支持高并发点查,HBase 这类数据库天然合适;如果查询场景偏分析型,也可以引入 Presto 等查询引擎做联邦查询。总之,技术选型的目标是让“每条管道上的工具”各司其职,并保证层与层之间的数据接口清晰。

选锅选灶看似琐碎,却决定整家餐厅的出餐效率。一个优秀的架构师,不会迷恋某一款工具,而是像经验丰富的厨师长一样,知道哪口锅炖肉、哪口锅爆炒,后厨才能转得又快又稳。

Lambda=BatchHadoop/Spark+SpeedFlink/Storm+ServingHBase\text{Lambda} = \text{Batch}_{\text{Hadoop/Spark}} + \text{Speed}_{\text{Flink/Storm}} + \text{Serving}_{\text{HBase}}
Lambda 架构 = 批处理工具 + 流处理工具 + 服务层存储。每个加数对应一个厨房的工种,缺一不可。
  1. \text{Batch}_{\text{Hadoop/Spark}}(批处理工具)负责全量计算的引擎,代表有 Hadoop MapReduce、Spark。
    Batch:Dall↦Vb\text{Batch}: D_{\mathrm{all}} \mapsto V_b
  2. \text{Speed}_{\text{Flink/Storm}}(流处理工具)负责秒级增量计算的引擎,代表有 Flink、Storm、Kafka Streams。
    Speed:Drecent↦Vs\text{Speed}: D_{\mathrm{recent}} \mapsto V_s
  3. \text{Serving}_{\text{HBase}}(服务层存储)支持低延迟随机查询的存储组件,代表有 HBase、Cassandra。
    Serving:{Vb,Vs}↦answer\text{Serving}: \{V_b, V_s\} \mapsto \text{answer}

应用场景

什么样的餐厅最适合双厨房模式?答案是那些“既要标准宴席、又要即时小吃”的客群。大数据场景里,这种需求比比皆是。最典型的是实时数仓:报表系统要靠批处理保证账实相符,大屏和看板却要秒级刷新的实时指标。Lambda 架构让数仓既能出具 T+1 精确报表,又能响应“当前实时 GMV 是多少”这类即时请求。

推荐系统是另一个完美场景。用户的长期偏好画像依赖海量历史行为数据,适合批处理慢慢建模;但“刚刚点过这个商品”的即时兴趣必须立刻影响下一次推荐,速度层正好接手。两条管道的结果合并后,推荐引擎才能同时做到“懂你的过去”和“懂你的此刻”。

监控告警则更依赖速度层。系统指标每秒钟都在产生,异常检测必须毫秒级响应。但事后审计和根因分析又需要批处理层提供小时级全量统计。Lambda 架构在这里的两路结合,既保证了实时告警不遗漏,也保证了历史回溯有据可查。判断一个场景是否适合 Lambda,核心标准就是:是否同时存在“等得起”的精确需求和“等不起”的实时需求。

场景千变万化,判断的尺子却只有一把:你既要“慢工出细活”,又要“立等可取”吗?如果是,双厨房就是你的答案;如果只是想要快,也许一个更简单的厨房就够了。

架构选择=f(延迟要求,复杂度承受,数据规模)\text{架构选择} = f(\text{延迟要求}, \text{复杂度承受}, \text{数据规模})
选 Lambda 还是 Kappa,取决于延迟要求、团队能承受的复杂度、以及历史数据的规模。这三个变量决定你要开几个厨房。
  1. \text{延迟要求}业务能否接受小时级以上的批处理延迟,决定速度层是否必须存在。
    延迟≤秒级⇒需要速度层\text{延迟} \le \text{秒级} \Rightarrow \text{需要速度层}
  2. \text{复杂度承受}团队是否有精力长期维护两套代码,决定 Lambda 的可行成本。
    复杂度≤团队容量⇒Lambda 可接受\text{复杂度} \le \text{团队容量} \Rightarrow \text{Lambda 可接受}
  3. \text{数据规模}历史数据是否庞大到重放不可承受,决定 Kappa 是否适用。
    规模太大⇒批处理层不可替代\text{规模太大} \Rightarrow \text{批处理层不可替代}

AI 应用场景

实时数仓的指标双路计算

阶段:数据管道设计与架构选型

解决的问题:传统数仓只能每天凌晨跑批,白天的业务决策看不到实时数据;而纯流式计算又无法保证历史全量报表的准确性。Lambda 架构用批处理层提供权威 T+1 报表,速度层提供秒级实时指标,服务层统一暴露查询接口,让分析师既能看到“今天的实时 GMV”,又能核对“过去的精确账单”。

具体案例:某电商平台建设实时数仓,Kafka 收集订单流后同时送入 Spark 批处理层和 Flink 流处理层。批处理层每天凌晨全量重算各品类销售报表,Flink 实时累加当日销售额。前端大屏从 HBase 读取合并结果,用户刷新时先展示速度层的实时近似值,随后批处理结果落地后自动校准为精确值。

推荐系统的行为画像合并

阶段:特征工程与在线推理

解决的问题:推荐系统依赖两类信号:长期兴趣画像(需要全量历史数据训练)和短期即时行为(必须毫秒级捕获取)。单独用批处理,画像更新太慢;单独用流处理,长期记忆容易丢失。Lambda 架构让长期画像走批处理层,短期行为走速度层,最终在推荐服务中合并打分。

具体案例:某短视频平台用批处理层每天更新用户类别偏好矩阵,用 Flink 速度层实时追踪用户最近 5 分钟的观看序列。当用户刷新推荐流时,服务层从 HBase 取出批处理画像分数,再叠加速度层的即时兴趣分,综合排序输出推荐结果,兼顾稳定与时效。

金融风控的实时与回溯双通道

阶段:实时决策引擎与离线模型训练

解决的问题:风控系统既要对每笔交易做毫秒级拦截,又要对全量交易样本做周期性模型重训和回溯分析。Lambda 架构的速度层负责实时规则与在线模型打分,批处理层负责全量特征计算和离线模型训练,服务层合并两路结果,让风控既快又准。

具体案例:某支付公司每笔交易先由 speed 层规则引擎在 50 毫秒内给出风险分,拦截明显异常。每天凌晨,批处理层对当天全部交易重新计算特征,修正模型参数,并把夜间新发现的坏样本回流到速度层规则库,形成“实时拦截 + 夜间校准”的闭环。

Python 演示

python35 行
import timeimport random  def run_lambda_demo(query_key=5, batch_interval=4, total_events=20, seed=42):    all_data = []   # 全量日志(批处理层输入,只追加不修改)    batch_map = {}  # 批处理结果视图    speed_map = {}  # 速度层临时补丁     rng = random.Random(seed)    for i in range(total_events):        value = rng.randint(1, 10)         # 新数据同时追加到全量日志和速度层        all_data.append(value)        speed_map[value] = speed_map.get(value, 0) + 1         # 每隔 batch_interval 条记录触发一次全量批计算        if (i + 1) % batch_interval == 0:            time.sleep(0.05)  # 模拟批处理耗时            batch_map = {}            for v in all_data:                batch_map[v] = batch_map.get(v, 0) + 1            speed_map = {}  # 批处理追平后清空速度层补丁         # 服务层合并两个视图        answer = batch_map.get(query_key, 0) + speed_map.get(query_key, 0)        print("事件%2d 查询%d 合并结果=%d" % (i + 1, query_key, answer))     print("最终批视图:", batch_map)    print("速度层补丁是否清空:", speed_map == {})  if __name__ == "__main__":    run_lambda_demo()
目的:用纯 Python 模拟 Lambda 架构的三个层——全量日志追加进批处理层,增量数据进速度层,服务层合并两者给出查询答案,并展示最终一致性(批处理追平后速度层补丁清空)。
  1. all_data = []定义不可变主数据日志,只追加不修改,对应批处理层的数据源。
  2. speed_map[value] = speed_map.get(value, 0) + 1新数据同时进入速度层,累加计数作为临时补丁。
  3. if (i + 1) % batch_interval == 0:每满一个批次周期,触发一次全量批处理。
  4. batch_map = {}批处理层从零开始对全量日志重新计数,模拟全量重算的“准确但慢”。
  5. speed_map = {}批处理追平后清空速度层补丁,避免重复计数,这正是最终一致性的体现。
  6. answer = batch_map.get(query_key, 0) + speed_map.get(query_key, 0)服务层合并两个视图,一个都不能少。

常见误区

误区我只要同时用了 Spark 和 Flink,就等于搭了一个 Lambda 架构,两个引擎各算各的就行了。

正解Lambda 架构的完整定义是三段式:批处理层产出批视图、速度层产出增量视图、服务层负责合并两者并提供统一查询。如果没有服务层把两路结果拼起来,用户面对的就是两套互相矛盾的数据,根本谈不上“一个答案”。就像餐厅只开两个厨房却没人上菜,客人只会饿着肚子。

为何常见:因为架构图最显眼的是两条计算管道,而合并层的职责容易被初学者忽略,误以为“并行”就是“架构”。

误区速度层算的是近似值,批处理层算的是精确值,两边永远不一致,那这个架构是不是有 bug?

正解这正是“最终一致性”的设计核心。速度层只是在批处理追平之前提供临时答案,一旦新的批视图落库,速度层对应补丁会被清空,系统自动回归权威的批处理结果。整个过程是有节奏的收敛,不是永久分裂。时间越久,速度层的补丁区间越小,直到趋近于空。

为何常见:初学者容易用“强一致”的心态理解分布式系统,看到短时间内两边数据不同就紧张,其实在 Lambda 的语境里“最终一致”才是正常态。

误区Kappa 只用一套流引擎,代码少、运维简单,肯定全面优于 Lambda,我应该无脑选 Kappa。

正解Kappa 的简洁建立在“重放全量日志”可行这个前提上。如果历史数据有数万亿条,重放一次要用好几天,Kappa 的效率和成本就会急剧恶化。Lambda 的批处理层恰恰擅长处理超大规模全量计算。两种架构是不同约束下的权衡,不是优劣之分。

为何常见:很多人被 Kappa 的“优雅”吸引,却忽略了它隐藏的重放代价,属于典型的“概念简化导致的选型偏误”。

误区Lambda 架构应该追求“一份代码,既能跑批又能跑流”,不然就不够先进。

正解Lambda 架构的核心目标是把准确性和实时性解耦,它并不要求两个层共用代码。事实上,批处理和流处理的数据模型、执行模型差异很大,强行用一套代码反而会两头不讨好。现代有 Flink 等引擎支持批流一体,但那是工具层面的优化,不是 Lambda 架构的定义要求。

为何常见:近来“批流一体”概念流行,容易让人与“Lambda 必须一体”混淆,实际上 Lambda 恰恰是以“分开”为基础的。

面试题

Q1. Lambda 架构中批处理层的主要职责是什么?

单选题 · easy

  • A.对全量历史数据做准确计算← 正确答案
  • B.只处理几秒钟延迟的新数据
  • C.把两张数据库表执行连接
  • D.直接对用户做实时推荐

答案:A。批处理层负责对不可变的全量历史数据进行计算,产出准确的批视图,这是最终一致性的权威来源。B 是速度层的职责,C 是普通数据查询操作,D 属于服务层或应用层的功能。

Q2. 以下哪一项不属于 Lambda 架构的三个核心层?

单选题 · easy

  • A.批处理层(Batch)
  • B.速度层(Speed)
  • C.服务层(Serving)
  • D.存储层(Storage)← 正确答案

答案:D。Lambda 架构的标准分层是批处理层、速度层和服务层。存储层是一个通用概念,不是 Lambda 特有的命名分层。HBase 等存储组件隶属于服务层的一部分,不能单独作为核心层。

Q3. 速度层存在的最主要动机是什么?

单选题 · mid

  • A.让全量数据更快被重新计算
  • B.弥补批处理层的延迟空缺← 正确答案
  • C.替代 HBase 提供低延迟访问
  • D.保证历史日志的不可变性

答案:B。批处理层计算周期长,速度层用流式计算处理最近新增数据,填补批处理尚未覆盖的时间窗口,让查询尽量实时。A 是批处理层自己的事,C 是服务层职能,D 是数据源的属性,都不是速度层的存在意义。

Q4. 服务层合并批视图与速度视图时,正确的处理原则是什么?

单选题 · mid

  • A.以批处理为基准,用速度层补齐新增区间← 正确答案
  • B.以速度层为准覆盖批处理所有结果
  • C.丢弃速度层数据只保留批处理结果
  • D.丢弃批处理结果只保留速度层数据

答案:A。批视图是准确基线,速度层只是补丁。服务层把批视图的权威结果取出来,再叠加尚未被批处理覆盖的新增数据。B 会引入不精确结果,C 会丢失实时性,D 则彻底放弃准确性,都属于错误合并策略。

Q5. Kappa 架构相对于 Lambda 架构的主要简化点是什么?

单选题 · hard

  • A.用一套流处理引擎完成全部计算← 正确答案
  • B.取消服务层改由客户端自取数
  • C.把批处理层数据迁移到对象存储
  • D.用 MySQL 替代 HBase 提供服务

答案:A。Kappa 的核心思想是完全去掉批处理层,让流处理引擎通过重放日志完成全量计算,从而只维护一套计算代码。B 和 D 都是对服务层的错误改动,C 只是存储介质换了一下,都不构成 Kappa 的本质简化。

Q6. 当批处理层的最新结果追上速度层的补丁区间时,系统会发生什么?

单选题 · mid

  • A.速度层补丁被清空,批视图成为权威答案← 正确答案
  • B.速度层补丁继续叠加,产生双份数据
  • C.服务层需要重启才能加载新视图
  • D.系统切换为只读模式暂停查询

答案:A。批处理追加完成后,服务层切换到以批视图为准,并删除速度层对应区间的补丁,避免重复计算,这正是最终一致性的实现。B 是设计缺陷,C 和 D 完全没有必要,服务层的热切换是常态。

Q7. Lambda 架构强调历史数据不可变,最主要的收益是什么?

单选题 · extreme

  • A.可安全全量重算并保证视图任意时刻可复现← 正确答案
  • B.能显著降低批处理层的存储成本
  • C.能避免流处理引擎做状态管理
  • D.能让服务层不建立任何索引

答案:A。不可变数据意味着历史日志不会被修改或删除,因此任何时刻都可以从头重跑一遍批处理,得到可复现的准确视图,这是 Lambda“最终正确”的根基。B 与不可变无关,C 和 D 更是与数据特性没有直接因果。

Q8. 以下哪些属于 Lambda 架构中服务层的职责?

多选题 · mid

  • A.对批视图建立高效索引← 正确答案
  • B.合并批视图与速度层补丁← 正确答案
  • C.对外提供低延迟统一查询← 正确答案
  • D.实时消费并汇总 Kafka 消息流

答案:ABC。服务层负责存储批视图、建立索引、合并速度层补丁,并对外暴露低延迟查询接口。D 是速度层的职责,服务层不负责消费原始消息流,只负责把两个计算结果合并起来回答用户的查询。

Q9. 以下哪些情况更适合选择 Lambda 架构?

多选题 · mid

  • A.既有精确全量报表又有实时看板需求← 正确答案
  • B.历史数据庞大且需要随时全量重算← 正确答案
  • C.只须对新数据做几秒级频率统计
  • D.团队能长期维护两套计算代码← 正确答案

答案:ABD。Lambda 适合同时有精确与实时诉求的场景,也适合历史数据大、重放成本高、需要独立批处理层的场景,代价是两套代码的维护成本。C 只有实时需求,用纯流处理即可,没必要引入 Lambda。

Q10. 关于 Lambda 架构与 Kappa 架构的说法,正确的有哪些?

多选题 · hard

  • A.Lambda 的延迟通常低于 Kappa,因为多了批处理层
  • B.Kappa 用流引擎重放日志来取代批处理层← 正确答案
  • C.Kappa 需要把全部历史数据保留在消息队列中← 正确答案
  • D.Lambda 通常需要维护两套计算代码,运维更复杂← 正确答案

答案:BCD。Kappa 用流引擎重放日志替代批处理,因此必须把历史数据保留在 Kafka 等消息队列中,这是它的隐藏前提。Lambda 因为双管道并行,通常需维护两套代码。A 说反了,Lambda 的批处理延迟恰恰是它引入速度层的原因,Kappa 的流重放也未必更快。