Lambda 架构详解:原理、三层结构与实战经验

Lambda 架构,说白了就是一套「既要又要」的解决方案。既要批处理的准确性,又要流处理的低延迟。我在 2016 年第一次接触这个架构时,觉得它有点笨重——为什么要把数据跑两遍?后来在实时数仓项目里被数据一致性问题折磨了三个月,才明白 Lambda 的良苦用心。

Lambda 架构的核心原理

Nathan Marz 提出 Lambda 架构时,核心思想就一句话:用批处理保证最终正确,用流处理保证实时可见。它把数据 pipeline 拆成三条路径:

  • 批处理层(Batch Layer):全量数据重算,产出不可变的 master 数据集
  • 速度层(Speed Layer):实时增量计算,补偿批处理的延迟
  • 服务层(Serving Layer):合并批视图和实时视图,对外提供统一查询

你想想看,这三层各司其职,有点像公司的「财务部+前台+报表系统」。财务部月底才出总账(批处理),前台每天记流水(速度层),报表系统把两者合并给你看(服务层)。

核心公式:Query = function( BatchView ∪ RealTimeView )

用户看到的最终结果,是批处理视图和实时视图的合并。批处理负责修正,实时负责新鲜。

Lambda 架构三层结构 数据源(Kafka) 批处理层(Batch Layer) Spark / MapReduce / Hive 速度层(Speed Layer) Flink / Storm / Kafka Streams 不可变 Master 数据集 实时增量视图 服务层(Serving Layer) Druid / Cassandra / HBase 批处理层产出不可变全量数据,速度层补偿实时增量,服务层合并对外提供服务

批处理层(Batch Layer)

批处理层是 Lambda 架构的「压舱石」。它的职责很简单:存储原始数据,定期全量重算。我习惯用 HDFS 或 S3 存原始日志,然后用 Spark 每天凌晨跑一次全量 ETL。

这里有个关键设计——数据不可变。原始数据一旦写入,永远不允许修改或删除。为什么?因为批处理层要保证「无论什么时候重算,结果都一样」。如果数据能改,那今天跑和明天跑的结果可能不同,整个架构的可靠性就崩塌了。

我的经验:批处理层不要只存「处理后的数据」,一定要保留原始日志。我曾经遇到一个需求,要回溯三个月前的用户行为路径,幸好原始数据还在,否则只能跟业务方说「抱歉,算不了」。

批处理层的典型技术栈:

组件 用途 我常用的版本
HDFS / S3 原始数据存储 HDFS 3.x / S3 Standard
Spark / Hive 全量计算引擎 Spark 3.x + Hive 3.x
Parquet / ORC 列式存储格式 Parquet + Snappy 压缩

速度层(Speed Layer)

速度层存在的唯一理由——批处理太慢了。批处理层跑一次全量可能要几个小时,但业务方要的是秒级延迟。速度层就是用来填补这个时间差的。

速度层只处理「批处理还没覆盖到的新数据」。比如批处理每天凌晨 2 点跑完昨天的全量,那速度层就负责处理今天 0 点到现在的实时数据。等明天批处理跑完,速度层的数据就可以丢弃了。

注意:速度层的结果是「近似正确」的。因为它只看到部分数据,无法做全局聚合。比如计算「今日 UV」,速度层只能给出近似值,精确值要等批处理层来修正。

速度层的典型实现:

  • Flink:我最常用的实时计算引擎,支持 exactly-once 语义
  • Kafka Streams:轻量级方案,适合简单的状态计算
  • Spark Streaming:微批次模式,延迟在秒级到分钟级

举个实际例子。我在电商项目中用 Flink 做实时订单统计,每 10 秒输出一次「最近 5 分钟的订单金额」。这个值是不精确的——因为可能有延迟到达的订单。但业务方看实时大屏,要的就是「大概齐」,精确数据等明天批处理修正就行。

服务层(Serving Layer)

服务层是 Lambda 架构的「门面」。它负责把批处理层的全量结果和速度层的增量结果合并,对外提供统一的查询接口。

服务层需要支持:

  1. 低延迟查询:用户点开报表,不能等 10 秒才出数据
  2. 数据合并:批视图 + 实时视图 = 最终结果
  3. 高并发:可能同时有几百个查询请求

我常用的服务层组件是 Druid 和 Cassandra。Druid 适合 OLAP 场景,预聚合能力强;Cassandra 适合 KV 查询,写入吞吐高。

合并逻辑示例:

-- 批处理层产出:昨日之前的全量聚合
SELECT date, SUM(amount) AS total
FROM batch_order_summary
WHERE date < '2024-01-15'
GROUP BY date;

-- 速度层产出:今日的实时增量
SELECT '2024-01-15' AS date, SUM(amount) AS total
FROM realtime_order_stream
WHERE window_start >= '2024-01-15 00:00:00';

-- 服务层合并:UNION ALL 即可
SELECT date, total FROM batch_view
UNION ALL
SELECT date, total FROM realtime_view;

优缺点分析

Lambda 架构不是银弹。我用它做过三个项目,有成功的,也有踩坑的。下面是我的真实感受。

维度 优点 缺点
数据准确性 批处理保证最终一致,不会丢数据 实时视图和批视图可能不一致,需要合并逻辑
延迟 速度层提供秒级响应 批处理层延迟大(小时级)
开发成本 批和流可以独立开发 同一逻辑要写两遍,维护成本翻倍
运维复杂度 各层职责清晰,问题定位快 需要维护两套 pipeline,资源消耗大
扩展性 批和流可以独立扩缩容 服务层合并逻辑可能成为瓶颈

我曾经踩过的坑:在速度层和批处理层使用不同的聚合逻辑。批处理用 Hive SQL,速度层用 Flink SQL,结果两个引擎对「去重计数」的实现有细微差异,导致合并后的数据对不上。后来我强制要求:批和流的业务逻辑必须用同一套代码生成,或者至少用同一套 UDF。

Lambda 架构最适合的场景:

  • 对数据准确性要求极高(如金融、计费)
  • 既有历史全量分析需求,又有实时监控需求
  • 团队有足够的资源维护两套 pipeline

不适合的场景:

  • 只需要实时,不需要历史回溯(用纯流处理即可)
  • 只需要离线报表,不需要秒级延迟(用纯批处理即可)
  • 团队小,维护两套代码吃力(考虑 Kappa 架构)

嗯,Lambda 架构就讲到这里。它虽然笨重,但在很多场景下仍然是「最稳」的选择。下一层我们聊聊它的进化版——Kappa 架构,看看怎么用一套 pipeline 解决所有问题。