数仓来源
为什么要数仓
数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。
是为了统计,决策!
数仓中表是面向主题的
后端思维 (面向应用):
你在设计 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。
总结
维度建模
事实表和维度表
维度表是名词(实体/性质),事实表是动词(动作/事件)。
这个例子选得非常好。我们用 “2023-11-11 购买 iPhone 15” 这个具体的业务场景,把抽象的概念直接落地成数据库里的 “行”和“列”。
在数据仓库(特别是星型模型)中,这条数据会被拆解并分别存储在 维度表 和 事实表 中。
1. 维度表 (Dimension Table):存储“环境描述”
核心作用: 它是你的查询字典。它告诉我们“ID 是 P_1001 的商品到底长什么样”。
维度表存储的是业务实体的属性信息(文本描述为主)。对于 iPhone 15,我们会存入一张商品维度表(比如 dim_product)。
表结构模拟:
存储内容: 商品的静态描述信息。注意,这里不存你今天卖了多少钱,只存“iPhone 15 是 Apple 生产的黑色手机”这个事实。
数据特点: 相比事实表,数据量小(商品 SKU 有限),变化慢。
用途: 用来做 SQL 中的 过滤 (WHERE) 和 分组 (GROUP BY)。
例子: 当你想查“Apple 品牌的销量”时,数仓会先来这张表找到
brand_name = 'Apple'对应的product_id。
2. 事实表 (Fact Table):存储“动作与度量”
核心作用: 它是你的流水账本。它记录“发生了什么事”以及“数值是多少”。
事实表存储的是业务过程(下单)产生的数据度量。对于这笔交易,我们会存入一张订单事实表(比如 dwd_fact_order)。
表结构模拟:
存储内容:
外键 ID:
user_id,product_id,date_id。注意,这里不存“iPhone 15”这个字符串,只存 IDP_1001。这能极大节省存储空间。度量值 (Metrics):
8999.00(金额),1(数量)。这是可以用来做加减乘除数学运算的。
数据特点: 数据量巨大,每天新增千万条记录。
用途: 用来做 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) 去流水账里筛选。找到所有匹配的行。
聚合计算:
把筛选出来的行里的
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 和 钱。这是星星的“核心”。
注:这里看不出是哪个城市的,只有 ID。
⭐ B. 射线:维度表 (dim_user)
把省份、城市全部“压扁”放进来。这是星星的“光芒”。
注:你看,"北京市" 重复出现了(数据冗余),但没关系,我们要的是方便。
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 = '北京市'; -- 直接在维表里过滤总结
在这个例子中:
事实表负责记录“收了 25 块钱”。
维度表负责记录“U_001 是北京市的”。
星型模型就是让你只 Join 一次就能算出结果,用存储空间(User表里重复存北京)换取了查询时间(少 Join 两张表)。
ETL与调度
ETL 代表的是数据在进入数据仓库之前必须经历的三个阶段:
好的!我是“大数据领航员”。我将为您精炼和重构这部分知识,使之成为逻辑清晰、重点突出的面试标准答案。
在面试中,对于 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 依赖管理来严格控制数据链路,确保上游数据就绪后再触发下游计算,从而根本上解决数据错乱和质量问题。”