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 架构更稳妥。别盲目追新。
为了让你更直观地理解,我画了一张对比图:
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 架构也一样,只是少了批处理那条线。
说白了,架构解决的是“数据怎么来”,分层解决的是“数据怎么存”。两者配合,才能构建一个既灵活又可靠的数据湖。