数仓来源

为什么要数仓

数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。

是为了统计,决策!

数仓中表是面向主题的

  • 后端思维 (面向应用):

    你在设计 MySQL 表时,通常是按功能模块设计的。

    • 例子: user_service 库里有用户表,order_service 库里有订单表,log_service 库里有日志表。这些表是为了配合你的 Java微服务 运行的。

  • 数仓思维 (面向主题):

    数据仓库不在乎你是哪个微服务产生的,它只在乎分析对象。

    • 例子: 数仓会建立一个 “客户 (Customer)” 主题。它会把 user_service 里的注册信息、order_service 里的收货地址、log_service 里的最近登录时间,全部清洗后放在一起,形成一个全方位的“客户画像”。

    • 目的: 提供对分析对象的一个完整、一致的描述。

数仓中数据来源

数据进入数据仓库之前,必然要经过统一与综合。

这是后端工程师最容易理解的痛点。

  • 现状 (后端乱象):

    • 系统 A (Java): 性别字段用 0/1 (int)。

    • 系统 B (Python): 性别字段用 'M'/'F' (char)。

    • 系统 C (老系统): 日期格式是 2023/11/22

    • 系统 D (新系统): 日期格式是 1700640000 (时间戳)。

  • 数仓规范 (集成):

    数据仓库就像一个“强迫症的收纳师”。不管你源头是什么样,进到数仓必须统一:

    • 性别统一为 Male/Female

    • 日期统一为 yyyy-MM-dd

    • 核心价值: 解决了同名异义、异名同义、单位不统一等矛盾。只有这样,分析师才能放心地写 SQL 聚合。

架构分层

1. ODS 层

  • 英文全称: Operational Data Store

  • 中文名称: 数据运营层

  • 含义: 最接近数据源的一层,存放接入的原始数据。

2. DW 层 (总称)

  • 英文全称: Data Warehouse

  • 中文名称: 数据仓库层

  • 含义: 核心设计层,通常包含 DWD, DWM, DWS 三个子层。

2.1 DWD 层

  • 英文全称: Data Warehouse Detail

  • 中文名称: 数据明细层

  • 含义: 保持和 ODS 一样的数据粒度,但进行清洗和质量保证。

2.2 DWS 层

  • 英文全称: Data Warehouse Service (注:原文拼写为 Servce,标准应为 Service)

  • 中文名称: 数据服务层

  • 含义: 又称数据集市或宽表,按照业务划分(如流量、订单),生成字段较多的宽表。

4. APP 层

  • 英文全称: Application

  • 中文名称: 数据应用层

  • 含义: 完全为了满足具体的分析需求而构建的数据,通常是高度汇总的数据。

实例

你好!我是“大数据领航员”。

这部分是数仓设计的核心。很多后端转大数据的同学最容易晕的就是:“为什么不直接从 ODS 算结果?非要搞个 DWD、DWS 这么麻烦?”

我们通过一个具体的“外卖订单”场景,带着一条数据走完这三层,你就明白每一层的存在意义了。


场景设定:

业务动作: 用户“张三”在“2023-11-22 12:30”点了一份“隆江猪脚饭”,支付了 25 元。

1. ODS 层

关键词: 原封不动、备份。

  • 数据长什么样?

    你的业务数据库(MySQL)里的 order_info 表是什么样,这里就是什么样。可能连字段名里的拼写错误都一模一样。

     // ODS 层的一行数据 (几乎就是 MySQL Binlog 的转储)
     {
       "id": "10086",
       "uid": "u_9527",        // 关联用户ID
       "shop_id": "s_888",     // 关联店铺ID
       "pay_amt": 2500,        // 这里的单位是“分” (后端常见设计)
       "status": 1,            // 1 代表已支付 (魔法数字)
       "ctime": 1700627400000, // 时间戳
       "ext": "{\"spicy\": 1}" // 扩展字段 JSON
     }
  • 后端视角的价值:

    “后悔药”。 如果你后面的代码写错了,或者想换一种分析逻辑,原始数据还在,随时可以重跑。

2. DW 层

这一层要把“不可读”的数据变成“好用”的数据。我们拆开看:

2.1 DWD 层

关键词: 去脏、统一格式、反魔法值。

  • 做什么?

    • 清洗: 比如发现有一条数据的 pay_amt 是负数(脏数据),这一层直接过滤掉。

    • 转换: 把“分”转成“元”;把时间戳转成 yyyy-MM-dd

    • 维度退化: 这是一个高级词。比如 ext 里的 spicy: 1,在 ODS 里是 JSON 字符串,很难查。在 DWD 层,我们会把它提取出来,变成一个独立的字段 is_spicy

  • 数据长什么样?

     -- DWD 层的数据 (变得像人看的了)
     order_id: "10086"
     user_id: "u_9527"
     shop_id: "s_888"
     pay_amount: 25.00       -- 变成了元
     order_status: "PAID"    -- 翻译了状态码 1 -> PAID
     create_time: "2023-11-22 12:30:00" -- 变成了时间字符串
     is_spicy: 1             -- 从 JSON 里提出来的
     dt: "2023-11-22"        -- 分区字段

2.2 DWS 层

关键词: 空间换时间、反范式、宽表。

  • 做什么?

    这是后端最不习惯的一层。在 MySQL 里,你想知道“张三是哪里人”,你会 Join user_table。但在大数据里,几亿行 Join 是灾难。

    所以,我们在这一层提前把 Join 做好。把用户表、店铺表的信息,全部“甚至冗余”地拼到订单表上来。

  • 数据长什么样? (注意看,这行数据变得巨宽无比)

     -- DWS 层宽表 (dws_trade_order_detail_day)
     -- 核心订单信息
     order_id: "10086"
     pay_amount: 25.00
     ​
     -- 拼进来的【用户】信息 (冗余存储)
     user_id: "u_9527"
     user_name: "张三"      -- 以后查名字不用 Join 了
     user_gender: "男"
     user_city: "北京"
     ​
     -- 拼进来的【店铺】信息 (冗余存储)
     shop_id: "s_888"
     shop_name: "隆江猪脚饭(西二旗店)"
     shop_category: "快餐"
     ​
     dt: "2023-11-22"
  • 后端视角的价值:

    “自助餐台”。 分析师想看“北京男用户的消费总额”?直接查这张表 WHERE user_city='北京' AND user_gender='男' 即可,完全不需要 Join。效率提升 100 倍。

APP 层

关键词: 高度汇总、结果集。

  • 做什么?

    老板要看大屏:“全国各城市的实时销售额排名”。你不能每次都去 DWS 层扫几亿行数据算 SUM。

    所以,我们提前算出结果,存在 APP 层(通常同步回 MySQL 或 Redis)。

  • 数据长什么样?

     -- APP 层 (app_city_sales_daily)
     city_name: "北京"
     total_gmv: 1000000.00
     order_cnt: 50000
     dt: "2023-11-22"
  • 后端视角的价值:

    “API 接口数据源”。 你的 Java 代码写的接口:

    public SalesVO getCitySales(String date)

    里面执行的 SQL 就是:SELECT * FROM app_city_sales_daily WHERE dt = ?。

    响应时间:10ms。

总结

层级

就像做菜...

如果没有这一层...

ODS

刚买回来的带泥土豆

数据错了没法回溯,只能去 MySQL 生产库查,会被运维打死。

DWD

洗净削皮切好的土豆丝

每个人都要自己写代码去解析“分转元”、去解析 JSON,重复造轮子。

DWS

半成品预制菜(土豆肉丝)

每次查报表都要把几亿张订单和用户表 Join 一次,集群资源瞬间耗尽。

APP

端上桌的成品菜

报表加载速度极慢,老板点开页面要等 5 分钟。

维度建模

事实表和维度表

维度表是名词(实体/性质),事实表是动词(动作/事件)。

这个例子选得非常好。我们用 “2023-11-11 购买 iPhone 15” 这个具体的业务场景,把抽象的概念直接落地成数据库里的 “行”和“列”

在数据仓库(特别是星型模型)中,这条数据会被拆解并分别存储在 维度表事实表 中。

1. 维度表 (Dimension Table):存储“环境描述”

核心作用: 它是你的查询字典。它告诉我们“ID 是 P_1001 的商品到底长什么样”。

维度表存储的是业务实体的属性信息(文本描述为主)。对于 iPhone 15,我们会存入一张商品维度表(比如 dim_product)。

表结构模拟:

product_id (主键)

product_name (名称)

brand_name (品牌)

category (品类)

color (颜色)

launch_date (上市时间)

P_1001

iPhone 15

Apple

手机

黑色

2023-09

  • 存储内容: 商品的静态描述信息。注意,这里不存你今天卖了多少钱,只存“iPhone 15 是 Apple 生产的黑色手机”这个事实。

  • 数据特点: 相比事实表,数据量小(商品 SKU 有限),变化慢。

  • 用途: 用来做 SQL 中的 过滤 (WHERE)分组 (GROUP BY)

    • 例子: 当你想查“Apple 品牌的销量”时,数仓会先来这张表找到 brand_name = 'Apple' 对应的 product_id

2. 事实表 (Fact Table):存储“动作与度量”

核心作用: 它是你的流水账本。它记录“发生了什么事”以及“数值是多少”。

事实表存储的是业务过程(下单)产生的数据度量。对于这笔交易,我们会存入一张订单事实表(比如 dwd_fact_order)。

表结构模拟:

order_id (主键)

user_id (谁买的)

product_id (买了啥)

date_id (何时买)

payment_amount (金额)

quantity (数量)

O_20231111_01

U_888

P_1001

20231111

8999.00

1

  • 存储内容:

    1. 外键 ID: user_id, product_id, date_id。注意,这里不存“iPhone 15”这个字符串,只存 ID P_1001。这能极大节省存储空间。

    2. 度量值 (Metrics): 8999.00 (金额), 1 (数量)。这是可以用来做加减乘除数学运算的。

  • 数据特点: 数据量巨大,每天新增千万条记录。

  • 用途: 用来做 SQL 中的 聚合计算 (SUM, COUNT, AVG)

    • 例子: 最终计算“销售额”时,是把这张表里的 payment_amount 加起来。

3. 它们如何配合工作?

当你作为后端工程师,想在数仓里执行这个查询:

“统计 2023-11-11 当天,Apple 品牌的销售总额。”

数仓的执行逻辑是这样的:

  1. 查维度表 (dim_product):

    • 找到 brand_name = 'Apple' 的记录。

    • 提取出对应的 ID:P_1001 (iPhone 15), P_1002 (iPhone 14)...

  2. 查事实表 (dwd_fact_order):

    • 带着上面找到的 ID (P_1001 等) 和时间条件 (20231111) 去流水账里筛选。

    • 找到所有匹配的行。

  3. 聚合计算:

    • 把筛选出来的行里的 payment_amount (8999, 7999...) 全部 SUM 起来。

总结

  • 维度表存的是:iPhone 15, Apple, 黑色 (描述信息,用于筛选)。

  • 事实表存的是:P_1001, 8999, 1 (ID 和数字,用于计算)。

这种“事实表 + 维度表”的设计模式,就是文档中提到的“星型模型”,它是数仓中效率最高、最常用的建模方式。

星型模型

注意:事实表大部分都是ID或者数字

场景: 我们要分析 “瑞幸咖啡”“北京市” 的销售总额。

1. 以前你的做法(后端 MySQL 思维)

你会把数据拆得非常散,为了不冗余(雪花模型):

  • Order表:user_id

  • User表:city_id

  • City表:province_id

  • Province表:province_name ("北京市")。

❌ 查询痛苦: 你需要 JOIN 4 张表 才能把“订单”和“北京市”关联起来。

2. 星型模型的做法(数仓 Hive 思维)

我们在数仓里只建 1 张中心事实表1 张扁平的用户维度表

⭐ A. 核心:事实表 (fact_order)

只存 ID 和 钱。这是星星的“核心”。

order_id

user_id (FK)

product_id (FK)

amount (度量)

1001

U_001

P_Latte

25.00

1002

U_002

P_Americano

15.00

1003

U_001

P_Latte

25.00

  • 注:这里看不出是哪个城市的,只有 ID。

⭐ B. 射线:维度表 (dim_user)

把省份、城市全部“压扁”放进来。这是星星的“光芒”。

user_id (PK)

user_name

city_name

province_name

U_001

张三

朝阳区

北京市

U_002

李四

浦东新区

上海市

U_003

王五

海淀区

北京市

  • 注:你看,"北京市" 重复出现了(数据冗余),但没关系,我们要的是方便。

3. 实际中怎么用?(SQL 演示)

现在老板问:“北京市卖了多少钱?”

后端 MySQL 写法 (痛苦):

 SELECT sum(o.amount)
 FROM Order o
 JOIN User u ON o.user_id = u.id
 JOIN City c ON u.city_id = c.id      -- 多一次 Join
 JOIN Province p ON c.prov_id = p.id  -- 又多一次 Join,性能爆炸
 WHERE p.name = '北京市';

数仓 星型模型 写法 (爽快):

 SELECT 
     sum(f.amount) 
 FROM 
     fact_order f         -- 事实表
 JOIN 
     dim_user u           -- 维度表
     ON f.user_id = u.user_id
 WHERE 
     u.province_name = '北京市'; -- 直接在维表里过滤

总结

在这个例子中:

  1. 事实表负责记录“收了 25 块钱”。

  2. 维度表负责记录“U_001 是北京市的”。

  3. 星型模型就是让你只 Join 一次就能算出结果,用存储空间(User表里重复存北京)换取了查询时间(少 Join 两张表)。

ETL与调度

ETL 代表的是数据在进入数据仓库之前必须经历的三个阶段:

阶段

英文全称

中文含义

核心工作内容

后端视角类比

E

Extract

抽取

从各种数据源(如你的 MySQL 数据库、应用日志、第三方 API)中读取数据。

远程调用 DAO 层,执行 SELECT 命令,或监听 Binlog

T

Transform

转换/清洗

这是 ETL 的核心,对原始数据进行清洗、转换和整合。

编写 Service 层的业务逻辑,对数据进行格式校验、去重、去噪、单位统一(如把 0/1 转换为 Yes/No)、数据计算与聚合。

L

Load

加载

将经过清洗和转换后的数据,装入目标数据仓库(如 Hive 的某个分区)。

调用 Mapper/Writer 将处理好的数据批量 INSERT OVERWRITE 到下游表。

好的!我是“大数据领航员”。我将为您精炼和重构这部分知识,使之成为逻辑清晰、重点突出的面试标准答案。

在面试中,对于 ETL 和调度的问题,您需要体现出“后端思维”与“大数据思维”的差异,并强调 “效率”与“依赖” 这两个核心词。

面试精讲:ETL 与调度

这部分回答的核心思路是:先说方法(ETL),再说保证(调度)。

一、ETL 数据搬运策略 (E & L 阶段)

ETL 即 Extract(抽取)、Transform(转换)、Load(加载)。在数据抽取和加载(E & L)阶段,核心问题是:如何高效、低成本地将数据从业务库导入数仓?

1. 全量同步 (Full Load)

  • 概念: 每次抽取时,将源表中的全部数据进行导出和覆盖式加载。

  • 适用场景: 数据量小、变动频率不高的维度表(如地区、配置字典)。

  • 优点: 逻辑简单,数据一致性有保证(每天都是最新快照)。

  • 对比: 类似于后端服务启动时全量加载配置到缓存

  • 面试加分点: 需警惕全量同步对业务数据库的压力。

2. 增量同步 (Incremental Load)

  • 概念: 仅抽取源表中在上一调度周期内新增或发生变化的数据。

  • 适用场景: 数据量巨大的交易流水(订单、支付)等事实表。

  • 优点: 抽取速度快,对业务库(OLTP)压力小,符合大数据“只追加、不修改”的原则。

  • 对比: 类似于后端通过 create_timeupdate_time 字段进行条件查询。

  • 面试加分点: 难点在于如何处理 UPDATEDELETE 操作,通常需要通过监听 Binlog 或使用拉链表技术来维护历史快照。

二、调度系统与依赖管理 (核心 T 阶段保障)

大数据调度的核心挑战不是“时间”,而是如何确保上下游数据之间的依赖关系

1. 为什么不能依赖时间 (Cron)?

  • 痛点: 传统的定时任务基于时间执行,但在大数据场景下,上游 ETL 任务的完成时间不稳定(受数据量、集群资源影响)。

  • 后果: 如果下游任务在数据未完全导入时启动,会导致数据计算结果错误(如计算出大量 0),产生数据错乱和质量问题

2.核心机制:基于依赖的 DAG (有向无环图)

  • 作用: 调度系统的核心是构建 DAG,确保任务只有在所有上游依赖任务成功完成后才会被触发执行。

  • 流程体现: 确保数据流严格按照 ODS(原材料就绪)-> DWD -> DWS -> APP(成品输出) 的顺序流动。

  • 元数据的作用: 调度系统依赖元数据来记录和查询任务状态,以及定义数据抽取的时间安排。

3. 调度工具与对比

  • Airflow: 全球主流,基于 Python 代码定义任务(DAG),代码化、灵活性高。

  • DolphinScheduler(海豚调度): 国产主流,基于 Java 开发,界面可视化操作,易于非数据工程师上手。

4. 实战例子(加分项)

场景: 计算 DWS 层日报表。

依赖链: 任务 A (ODS同步) \rightarrow 任务 B (DWD清洗) \rightarrow 任务 C (DWS汇总)

调度逻辑: 任务 B 不在固定时间(如 03:00 AM)执行,而是监控任务 A 的状态。一旦任务 A 在 02:30 AM 成功完成,任务 B 立即启动,保证数据新鲜且准确

面试总结话术

“我的理解是:ETL 是数仓的生命线。维度表(配置)我选择全量同步;事实表(流水)我选择增量同步,以降低业务库压力。在调度上,我不会使用简单的 Cron 任务,而是采用 Airflow 或 DolphinScheduler,通过 DAG 依赖管理来严格控制数据链路,确保上游数据就绪后再触发下游计算,从而根本上解决数据错乱和质量问题。”

比较是偷走幸福的小偷