Skip to content
2026-05-111分钟big-datalakehousewarehouse

01|数据湖架构:ODS、DWD、DWS、ADS 怎么分层

大数据架构的第一原则是让数据从“原始事实”逐层走向“业务可消费资产”。很多团队踩的坑不是技术选型,而是分层混乱:一张表里既有原始日志字段又有业务口径,下游谁都不敢动,最后变成一坨没人维护的“屎山表”。本文以一套电商用户行为数据为例,讲清楚 ODS、DWD、DWS、ADS 每一层的边界、判断标准和湖仓一体的落地方式。

背景:为什么必须分层

假设业务方提了一个需求:统计每个商品每天的“加购转下单转化率”。如果不分层,你可能直接写一个几百行的 SQL,从 Kafka 落地的原始 JSON 日志一路 join 到结果。三个月后埋点字段变了、口径要调整、另一个团队要复用“加购明细”,这个 SQL 就成了灾难。

分层的本质是用存储换可维护性:每一层只做一件事,下游永远基于稳定的上游构建。

核心概念:四层分工与数据量级

层级目标典型内容更新方式量级参考
ODS保留原始数据日志、业务库 CDC、第三方数据增量追加单日 TB 级原始日志
DWD清洗成明细事实用户行为明细、订单明细分区覆盖比 ODS 小 30%~50%(去脏数据)
DWS面向主题汇总用户日汇总、商品日汇总分区覆盖比 DWD 小 1~2 个数量级
ADS面向应用交付看板表、推荐特征、报表宽表分区覆盖/全量MB~GB 级,直接给应用查

ODS 尽量少加工,DWD 统一口径,DWS 聚焦复用,ADS 追求交付效率。在一个真实电商场景里,ODS 行为日志单日约 1.2TB、80 亿行,到 DWD 用户行为明细约 0.7TB、46 亿行(过滤了爬虫和无效埋点),DWS 商品日汇总只剩约 1200 万行,ADS 看板表通常不到 50 万行。越往上层数据越小、查询越快、稳定性要求越高。

分层判断标准

一张表该放哪层,问三个问题:

  1. 是否保留原始字段和原始粒度。
  2. 是否已经表达稳定业务概念。
  3. 是否直接服务某个应用或看板。

如果答案依次是“是、否、否”,通常是 ODS;如果是“否、是、否”,通常是 DWD 或 DWS;如果是“否、是、是”,通常是 ADS。

实战 Demo:电商行为数据四层落地

下面是一套可直接在 Hive/Spark SQL 中运行的最小四层示例。

ODS 层——原始埋点日志,只做落地不做清洗:

sql
-- ods_user_event_log:直接映射 Kafka 落地的 JSON,字段全保留
CREATE TABLE IF NOT EXISTS ods.ods_user_event_log (
  raw_json STRING,
  kafka_offset BIGINT,
  ingest_time STRING
) PARTITIONED BY (dt STRING) STORED AS PARQUET;

DWD 层——解析、清洗、统一口径,得到明细事实表:

sql
INSERT OVERWRITE TABLE dwd.dwd_user_action_di PARTITION (dt='${dt}')
SELECT
  get_json_object(raw_json, '$.user_id')    AS user_id,
  get_json_object(raw_json, '$.item_id')    AS item_id,
  get_json_object(raw_json, '$.action')     AS action_type,   -- view/cart/order
  CAST(get_json_object(raw_json, '$.ts') AS BIGINT) AS event_ts
FROM ods.ods_user_event_log
WHERE dt = '${dt}'
  AND get_json_object(raw_json, '$.user_id') IS NOT NULL       -- 去脏数据
  AND get_json_object(raw_json, '$.is_robot') = 'false';       -- 去爬虫

DWS 层——按商品+日期主题汇总:

sql
INSERT OVERWRITE TABLE dws.dws_item_action_1d PARTITION (dt='${dt}')
SELECT
  item_id,
  SUM(IF(action_type='view',  1, 0)) AS view_cnt,
  SUM(IF(action_type='cart',  1, 0)) AS cart_cnt,
  SUM(IF(action_type='order', 1, 0)) AS order_cnt
FROM dwd.dwd_user_action_di
WHERE dt = '${dt}'
GROUP BY item_id;

ADS 层——直接产出业务指标,给看板查:

sql
INSERT OVERWRITE TABLE ads.ads_item_convert_1d PARTITION (dt='${dt}')
SELECT
  item_id,
  cart_cnt,
  order_cnt,
  ROUND(order_cnt / NULLIF(cart_cnt, 0), 4) AS cart_to_order_rate
FROM dws.dws_item_action_1d
WHERE dt = '${dt}';

运行命令(以 Spark 为例):

bash
spark-sql --master yarn -d dt=2026-05-10 -f build_item_convert.sql

预期输出:ads_item_convert_1d 分区 dt=2026-05-10 生成约 1200 万行,单行包含 item_id / cart_cnt / order_cnt / cart_to_order_rate,看板查询单商品耗时从未分层时的分钟级降到 200ms 以内。

进阶:湖仓一体与表格式选择

传统 Hive 表只能分区覆盖,无法行级更新,CDC 场景很难处理。湖仓一体(Lakehouse)用 Iceberg/Hudi/Delta 这类表格式解决了这个问题:

  • 写入频率高、需要 upsert(如订单状态变化):选 Hudi MOR 或 Iceberg,支持主键 upsert,避免每天全量重刷。
  • 以追加为主、查询为重:Iceberg COW 或 Delta,文件合并和元数据管理更省心。
  • 小文件治理:Iceberg 的 rewrite_data_files 可把单日产生的上万个小文件合并成 128MB~512MB 的大文件,下游扫描元数据耗时可下降 60% 以上。

建模建议:

  • 明细层尽量使用事实表和维表,不要提前塞太多业务判断。
  • 汇总层要控制粒度,避免按所有维度做全组合,否则 DWS 行数会爆炸。
  • 应用层可以冗余,但要注明来源和刷新频率。
  • 数据湖表格式要结合写入频率和更新模式选择,不要无脑全用一种。

小结

分层不是为了好看,而是为了让数据在出问题时能定位、在变化时能复用。记住一句话:ODS 不加工、DWD 统口径、DWS 重复用、ADS 拼交付。湖仓一体只是把这套分层的存储底座换成了支持 upsert 和小文件治理的现代表格式,分层思想本身不变。

0 条评论
Markdown

评论功能暂未开放

还没有评论

快来发表第一条评论吧

© 2026 A2DATA. All rights reserved.