3. 数据湖的架构:Lambda 与 Kappa,以及 Bronze/Silver/Gold 分层

聊完数据湖的基本概念,咱们得进入实操层面了。你想想看,一个数据湖建起来,数据往里灌,总得有个章法吧?不然就成了数据沼泽。我个人习惯,先定架构模式,再定分层标准。今天就把这两件事儿掰开揉碎讲清楚。

3.1 两种主流架构:Lambda 与 Kappa

先说 Lambda 架构。这玩意儿诞生得早,我大概 2015 年左右第一次在生产环境里用它。它的核心思想很简单:批流分离。

  • 批处理层(Batch Layer):负责全量历史数据。数据量大,延迟高,但结果准确。我习惯用 Spark 或 Hive 跑 T+1 的报表。
  • 流处理层(Speed Layer):负责实时增量数据。延迟低,秒级或分钟级出结果,但可能因为乱序或重复数据导致结果不精确。
  • 服务层(Serving Layer):把批和流的结果合并,对外提供统一查询。

核心痛点:你得维护两套代码。批处理一套逻辑,流处理一套逻辑。我曾经在一个项目里,就因为批和流对同一个“用户去重”逻辑理解不一致,导致报表对不上账,排查了整整两天。

后来 Kappa 架构出现了。它的主张更激进:只用一套流引擎。所有数据都走实时流,历史数据重算也通过“回放”流的方式解决。说白了,就是把批处理当成一个特殊的流(比如 Kafka 的 Consumer 从头消费)。

我的建议:如果你的业务对实时性要求极高(比如风控、实时大屏),且历史数据重算频率不高,Kappa 架构更清爽。但如果你有大量复杂的 ETL 和月度/年度汇总,Lambda 架构更稳妥。别盲目追新。

为了让你更直观地理解,我画了一张对比图:

Lambda 与 Kappa 架构对比 Lambda 架构 • 批处理层:全量历史数据,高延迟,高准确 • 流处理层:实时增量数据,低延迟,近似准确 • 服务层:合并批+流结果,统一查询 • 维护成本:高(两套代码) Kappa 架构 • 统一流引擎:所有数据走实时流 • 历史重算:通过回放流数据实现 • 服务层:直接查询流计算结果 • 维护成本:低(一套代码) 选型建议 • 实时性要求极高 + 历史重算少 → 选 Kappa • 复杂 ETL + 月度/年度汇总多 → 选 Lambda • 团队人力有限 → 优先 Kappa,降低维护成本 • 数据一致性要求极高 → Lambda 的批处理层更可靠

3.2 数据湖的典型分层:Bronze / Silver / Gold

架构定好了,数据怎么组织?这就得聊分层了。业内最通用的做法是 Medallion Architecture(奖章架构),也就是 Bronze、Silver、Gold 三层。我参与过的几个大型数据湖项目,无一例外都采用了这个模型。

3.2.1 Bronze 层(原始数据层)

这一层存的是“原汁原味”的数据。从源系统(数据库、日志、API)拉过来是什么样,就存成什么样。格式通常是 Parquet、Avro 或 Delta Lake。我建议不要做任何清洗,哪怕字段是 null,也原样保留。

避坑指南:我曾经为了省存储空间,在 Bronze 层就做了字段裁剪。结果后来业务方要回溯一个三个月前的原始日志,发现字段已经被我丢了,差点被投诉。记住,Bronze 层是数据湖的“后悔药”,千万别动它。

3.2.2 Silver 层(清洗与标准化层)

这一层开始做“家务活”。去重、类型转换、字段标准化、简单的数据质量检查。比如把“2024-01-01”和“01/01/2024”统一成标准日期格式。Silver 层的数据已经可以被分析师直接用了,但还不是最终业务模型。

3.2.3 Gold 层(业务聚合层)

这一层是给业务看的。按主题域(如用户、订单、商品)做宽表、聚合、预计算。BI 报表、数据产品、机器学习模型,都直接读 Gold 层。说白了,这是数据湖的“门面”。

我把三层的特点整理成了表格,方便你对比:

分层 数据状态 主要操作 使用者 存储格式建议
Bronze 原始、未清洗 仅追加(Append) 数据工程师 Delta Lake / Parquet
Silver 清洗、标准化 去重、类型转换、质量检查 数据分析师 Delta Lake
Gold 聚合、业务化 宽表、预计算、汇总 业务用户、ML 模型 Delta Lake / 视图

3.3 一个简单的分层实现示例

光说不练假把式。我用 Spark + Delta Lake 给你演示一下 Bronze 到 Silver 的典型操作。嗯,这里要注意,代码只是示意,生产环境要加更多校验。

# Bronze 层:直接读取源数据,原样写入
df_raw = spark.read.format("json").load("/data/raw/orders/")
df_raw.write.format("delta").mode("append").save("/data/bronze/orders/")

# Silver 层:清洗与标准化
df_bronze = spark.read.format("delta").load("/data/bronze/orders/")
df_silver = df_bronze \
    .dropDuplicates(["order_id"]) \
    .withColumn("order_date", to_date(col("order_date_str"), "yyyy-MM-dd")) \
    .withColumn("amount", col("amount").cast("decimal(10,2)")) \
    .filter(col("amount").isNotNull())
df_silver.write.format("delta").mode("overwrite").save("/data/silver/orders/")

# Gold 层:按用户聚合
df_silver = spark.read.format("delta").load("/data/silver/orders/")
df_gold = df_silver.groupBy("user_id").agg(
    sum("amount").alias("total_spent"),
    count("order_id").alias("order_count")
)
df_gold.write.format("delta").mode("overwrite").save("/data/gold/user_order_summary/")

个人经验:我习惯在 Silver 层用 Delta Lake 的 merge 操作来处理缓慢变化维度(SCD Type 2)。Bronze 层只用 append,保证历史可回溯。Gold 层则用 overwrite,因为业务聚合结果通常需要全量刷新。

3.4 架构与分层的结合

最后,我把 Lambda/Kappa 架构和 Bronze/Silver/Gold 分层结合起来看。你会发现,它们并不冲突。Lambda 的批处理层和流处理层,最终都会写入 Bronze 层(原始数据),然后经过 Silver 层清洗,最后汇聚到 Gold 层。Kappa 架构也一样,只是少了批处理那条线。

说白了,架构解决的是“数据怎么来”,分层解决的是“数据怎么存”。两者配合,才能构建一个既灵活又可靠的数据湖。