数仓来源
为什么要数仓
数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。
是为了统计,决策!
数仓中表是面向主题的
-
后端思维 (面向应用):
你在设计 MySQL 表时,通常是按功能模块设计的。
- 例子:
user_service库里有用户表,order_service库里有订单表,log_service库里有日志表。这些表是为了配合你的 Java微服务 运行的。
- 例子:
-
数仓思维 (面向主题):
数据仓库不在乎你是哪个微服务产生的,它只在乎分析对象。
- 例子: 数仓会建立一个 "客户 (Customer)" 主题。它会把
user_service里的注册信息、order_service里的收货地址、log_service里的最近登录时间,全部清洗后放在一起,形成一个全方位的"客户画像"。 - 目的: 提供对分析对象的一个完整、一致的描述。
- 例子: 数仓会建立一个 "客户 (Customer)" 主题。它会把
数仓中数据来源
数据进入数据仓库之前,必然要经过统一与综合。
这是后端工程师最容易理解的痛点。
-
现状 (后端乱象):
- 系统 A (Java): 性别字段用
0/1(int)。 - 系统 B (Python): 性别字段用
'M'/'F'(char)。 - 系统 C (老系统): 日期格式是
2023/11/22。 - 系统 D (新系统): 日期格式是
1700640000(时间戳)。
- 系统 A (Java): 性别字段用
-
数仓规范 (集成):
数据仓库就像一个"强迫症的收纳师"。不管你源头是什么样,进到数仓必须统一:
- 性别统一为
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。
- 例子: 当你想查"Apple 品牌 的销量"时,数仓会先来这张表找到
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 |
- 存储内容:
- 外键 ID:
user_id,product_id,date_id。注意,这里不存 "iPhone 15"这个字符串,只存 IDP_1001。这能极大节省存储空间。 - 度量值 (Metrics):
8999.00(金额),1(数量)。这是可以用来做加减乘除数学运算的。
- 外键 ID:
- 数据特点: 数据量巨大,每天新增千万条记录。
- 用途: 用来做 SQL 中的 聚合计算 (SUM, COUNT, AVG) 。
- 例子: 最终计算"销售额"时,是把这张表里的
payment_amount加起来。
- 例子: 最终计算"销售额"时,是把这张表里的
3. 它们如何配合工作?
当你作为后端工程师,想在数仓里执行这个查询:
"统计 2023-11-11 当天,Apple 品牌的销售总额。"
数仓的执行逻辑是这样的:
- 查维度表 (
dim_product):- 找到
brand_name = 'Apple'的记录。 - 提取出对应的 ID:
P_1001(iPhone 15),P_1002(iPhone 14)...
- 找到
- 查事实表 (
dwd_fact_order):- 带着上面找到的 ID (
P_1001等) 和时间条件 (20231111) 去流水账里筛选。 - 找到所有匹配的行。
- 带着上面找到的 ID (
- 聚合计算:
- 把筛选出来的行里的
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 写法 (痛苦):
sql
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 = '北京市';
数仓 星型模型 写法 (爽快):
sql
SELECT
sum(f.amount)
FROM
fact_order f -- 事实表
JOIN
dim_user u -- 维度表
ON f.user_id = u.user_id
WHERE
u.province_name = '北京市'; -- 直接在维表里过滤
总结
在这个例子中:
- 事实表负责记录"收了 25 块钱"。
- 维度表负责记录"U_001 是北京市的"。
- 星型模型 就是让你只 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_time或update_time字段进行条件查询。 - 面试加分点: 难点在于如何处理
UPDATE和DELETE操作,通常需要通过监听 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 依赖管理来严格控制数据链路,确保上游数据就绪后再触发下游计算,从而根本上解决数据错乱和质量问题。"