实时数仓分层架构:ODS、DWD、DWS、ADS 各层职责与数据流转

聊实时数仓,绕不开分层。很多刚接触实时流的同学会问:“为什么非得分这么多层?直接 Kafka 接 Flink 算完写 MySQL 不就行了?”

嗯,我早期也这么干过。结果呢?业务方要一个新指标,我得从头翻 Kafka 消息,逻辑散落在十几个 Flink 作业里,改一个字段影响一片。说白了,分层不是为了炫技,是为了让数据“好管、好用、好追溯”。

今天我就把实时数仓的四层架构——ODS、DWD、DWS、ADS,掰开揉碎讲清楚。每一层干什么、数据怎么流、有哪些坑,咱们一次聊透。

核心观点:实时数仓分层不是照搬离线,而是针对“流式处理”的特点做了取舍。ODS 保原始、DWD 做清洗、DWS 汇主题、ADS 出报表——每一层都有它存在的理由。

2.1 四层架构总览

先看一张整体图,后面咱们逐层拆解。

实时数仓四层架构图 实时数仓分层架构总览 数据源(业务DB/日志/消息) ODS 层(操作数据存储层) 原始数据缓冲,Kafka Topic 镜像 DWD 层(明细数据层) 清洗、去重、维度补齐、数据标准化 DWS 层(汇总数据层) 轻度聚合,宽表化,主题汇总 ADS 层(应用数据服务层) 指标计算,报表输出,对接业务系统 BI / 大屏 / 业务系统 数据流转方向 上游 → 下游 逐层加工,逐层抽象 每层只依赖上一层

这张图我画了很多遍。你注意看,数据是从上往下单向流动的。每一层只依赖它的上一层,不允许跨层引用。这样做的好处是:改底层逻辑不影响上层,排查问题也能快速定位。

2.2 ODS 层:原始数据的“缓冲区”

ODS,全称 Operational Data Store。说白了,就是把业务系统的数据原封不动搬进实时数仓。

我在项目中遇到过一种情况:业务方说“昨天的订单数据不对”,结果发现是 Flink 作业里做了隐式类型转换,把字符串转数字时丢了精度。如果 ODS 层保留了原始 JSON,直接回放就能对账。所以我的习惯是——ODS 层只做“搬运”,不做任何加工。

ODS 层核心职责

  • 数据缓冲:用 Kafka Topic 作为统一入口,解耦生产者和消费者。
  • 保真存储:保留原始消息体,包括所有字段、时间戳、元数据。
  • 多源汇聚:MySQL Binlog、业务日志、埋点数据……全部进 ODS。
  • 回溯能力:Kafka 消息保留一定周期(比如 7 天),方便重跑。

我的建议:ODS 层的 Topic 命名最好带上数据源标识。比如 ods_mysql_orders、ods_log_pageview。这样下游一看就知道数据从哪来,省去查文档的时间。

ODS 层数据流转示例

-- 模拟 ODS 层 Kafka Topic 中的一条订单消息(JSON 格式)
{
  "topic": "ods_mysql_orders",
  "partition": 3,
  "offset": 1024,
  "timestamp": 1712563200000,
  "value": {
    "order_id": "20250408001",
    "user_id": 10086,
    "product_id": "P998",
    "amount": 299.00,
    "status": "paid",
    "create_time": "2025-04-08 10:00:00"
  }
}

你看,这里连 partition 和 offset 都保留了。为什么?因为万一需要精确回溯某条消息,这些信息就是“坐标”。

2.3 DWD 层:数据“洗白白”的地方

DWD,Detail Warehouse Detail。这一层是实时数仓里最累的活。

ODS 层的数据是“脏”的:可能有重复、有空值、有格式不统一、有维度信息缺失。DWD 层要做的就是——清洗、去重、标准化、补齐维度。

DWD 层核心职责

  • 数据清洗:过滤无效数据、修正异常值。
  • 去重:基于业务主键做幂等处理,防止重复消费。
  • 维度补齐:关联维表(比如用户信息、商品信息),把 ID 变成可读的字段。
  • 格式统一:时间戳统一为毫秒级,金额统一为分,枚举值统一为中文。

注意:DWD 层的去重一定要用 Flink 的 ROW_NUMBER 或 Deduplicate 函数,配合事件时间 + Watermark。我曾经见过有人用处理时间去重,结果乱序数据一来,重复记录全跑进去了。

DWD 层数据流转示例

-- 经过 DWD 层处理后的订单明细(写入 Kafka Topic: dwd_order_detail)
{
  "order_id": "20250408001",
  "user_id": 10086,
  "user_name": "张三",          -- 维度补齐
  "product_id": "P998",
  "product_name": "智能手表",    -- 维度补齐
  "amount_cents": 29900,        -- 统一为分
  "status": "已支付",            -- 枚举转中文
  "event_time": 1712563200000,  -- 统一毫秒时间戳
  "dwd_process_time": 1712563201000
}

你对比一下 ODS 和 DWD 的数据,差别很明显。DWD 层的数据已经“干净”了,下游可以直接用。

2.4 DWS 层:轻量汇总,宽表化

DWS,Data Warehouse Summary。这一层做的是轻度聚合。

为什么要做轻度聚合?因为实时场景下,如果每次查询都去扫 DWD 的明细数据,性能扛不住。DWS 层把相同维度的指标提前算好,形成宽表。

DWS 层核心职责

  • 主题划分:按业务域组织,比如订单主题、用户主题、商品主题。
  • 轻度聚合:按分钟/小时粒度预计算 PV、UV、GMV 等指标。
  • 宽表设计:把多个维度的指标合并到一张表里,减少 Join。
  • 窗口计算:使用 Flink 的 Tumble / Hop / Session 窗口。

经验之谈:DWS 层的聚合粒度不要搞太细。我见过有人按秒聚合,结果 Kafka Topic 分区数暴涨,下游消费不过来。一般来说,分钟级聚合是性价比最高的选择。

DWS 层数据流转示例

-- DWS 层订单主题宽表(写入 Kafka Topic: dws_order_topic)
{
  "window_start": "2025-04-08 10:00:00",
  "window_end": "2025-04-08 10:01:00",
  "product_id": "P998",
  "product_name": "智能手表",
  "order_count": 15,
  "gmv_cents": 448500,          -- 15 * 29900
  "user_count": 12,
  "avg_order_amount": 29900
}

你看,这里已经算好了每分钟的订单数、GMV、用户数。ADS 层直接拿这些数据做报表,不用再算一遍。

2.5 ADS 层:面向应用,直接输出

ADS,Application Data Service。这一层是离业务最近的一层。

ADS 层的数据直接供给 BI 系统、大屏、推荐引擎、风控系统。所以它的特点是:查询快、格式灵活、接口友好。

ADS 层核心职责

  • 指标计算:基于 DWS 层做二次聚合,比如同比、环比、累计值。
  • 报表输出:按业务需求组织数据,比如实时大屏、日报看板。
  • 接口适配:输出格式适配下游系统,比如 JSON、Protobuf、JDBC。
  • 数据降维:只保留业务需要的字段,减少传输量。

一个小技巧:ADS 层的数据可以同时写入 Kafka(供实时消费)和 ClickHouse(供即席查询)。这样既满足了低延迟,又支持了复杂分析。

ADS 层数据流转示例

-- ADS 层实时大屏数据(写入 Kafka Topic: ads_realtime_dashboard)
{
  "report_time": "2025-04-08 10:01:00",
  "total_gmv_cents": 12500000,
  "total_orders": 420,
  "total_users": 380,
  "top_product": {
    "product_name": "智能手表",
    "gmv_cents": 448500,
    "order_count": 15
  },
  "gmv_growth_rate": 0.12       -- 同比上一小时
}

2.6 各层对比与选型建议

为了让你看得更清楚,我把四层的核心差异整理成了一张表。

分层 存储介质 数据粒度 主要操作 典型延迟
ODS Kafka 原始消息 采集、缓冲 秒级
DWD Kafka 明细事件 清洗、去重、维度补齐 秒级~分钟级
DWS Kafka / OLAP 轻度聚合 窗口聚合、宽表化 分钟级
ADS Kafka / OLAP / Redis 业务指标 二次聚合、报表输出 分钟级~准实时

从这张表你能看出:越往下游,数据粒度越粗,延迟越高,但查询性能越好。这就是分层架构的 trade-off。

2.7 避坑指南:我踩过的几个坑

聊了这么多理论,最后分享几个实战中容易踩的坑。

  • ODS 层 Topic 分区数设置不合理:分区数太少,写入瓶颈;太多,下游 Join 时 shuffle 压力大。我的经验是:按数据量预估,每个分区 10MB/s 左右吞吐。
  • DWD 层去重逻辑写错:我曾经用 PROCTIME() 做去重,结果乱序数据导致重复。后来改成 ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY event_time DESC) 才解决。
  • DWS 层聚合粒度太细:秒级聚合导致 Kafka 消息量暴增,下游 ClickHouse 写入跟不上。后来统一改为分钟级聚合,问题解决。
  • ADS 层直接查 DWD:有人图省事,ADS 层直接读 DWD 明细做计算。结果查询延迟从 100ms 飙到 5s。记住:ADS 层只依赖 DWS,不要跨层。

最后提醒一句:分层不是越多越好。如果你的业务简单,ODS → DWS → ADS 三层就够了。DWD 层可以合并到 ODS 或 DWS 里。别为了分层而分层,适合的才是最好的。