大数据数仓面试问题
问题一. 数仓为什么要分层,数仓分层的好处,分层的作用
💡 分层的核心思想就是解耦,再解耦,把复杂的问题简单化。
💡 分层本质:通过牺牲短期开发成本(20%-30%额外建模工作),换取系统长期演进能力。
数仓分层是大数据架构设计的核心理念之一,其优势体现在以下几个方面(按优先级排列):
1. 数据结构化治理(核心价值)
- 逐层抽象:通过ODS→DWD→DWS→ADS分层模型,实现原始数据→明细数据→聚合数据→应用数据的渐进加工。将复杂问题简单化。将一个复杂的业务加工逻辑拆解成多个步骤来分步完成,每一层只聚焦于某一类问题。当数据出现问题时,通过追溯可以很快定位到问题出现在哪一层,并且只需要对这一层逻辑修复即可。
- 质量管控:在DWD层统一实施字段标准/空值处理/数据稽核,避免下游污染(例:手机号格式统一为+86-138****1234)
- 血缘可溯:分层后可通过元数据工具(如Atlas)清晰跟踪指标口径的完整加工路径
2. 计算资源复用(成本优化)
- 中间沉淀:DWS层预存共性维度聚合结果(如日粒度UV、GMV),避免多个业务方重复计算原始日志
- 存储降本:分层可以支持数据的生命周期管理。数据仓库中的数据通常具有不同的生命周期,分层可以帮助对数据进行更好地管理和归档,确保数据的可用性和长期保存。ADS层按需保留热数据,历史数据自动归档至冷存储(高频高价值数据用贵存储,低频低价值数据用贱存储。对比:非分层模式下全量存储导致成本飙升300%)
3. 工程效能提升
将数据仓库按照不同的层级进行划分,可以根据需求优化每个层级的性能,使数据的查询和分析更加高效。用空间换时间,数据存储持久化,减少重复开发,提高数据的复用性。比如将稳定且通用的加工逻辑下沉到某一层,下游在使用时可以直接引用,提高数据查询效率。
-- 分层前:每次分析需从原始日志开始关联维表
SELECT /*+ 消耗120 core-hours */u.province,COUNT(DISTINCT o.user_id)
FROM raw_orders o
JOIN raw_users u ON o.user_id = u.id -- 原始表直接耦合
WHERE o.dt='2023-12-01';-- 分层后:直接使用DWS层聚合结果(仅消耗0.5 core-hours)
SELECT province, order_cnt
FROM dws_province_order_daily -- 预计算中间层
WHERE dt='2023-12-01';
4. 精细化权限控制
分层可以提供不同的数据访问方式和权限控制。将数据仓库分为不同的层级,可以根据用户的需求和权限将不同层级的数据暴露给用户,实现对数据的灵活访问和控制,同时确保敏感数据的安全性。
层级 访问角色 敏感数据处理方式
ODS 数据工程师 保留原始数据,RBAC强管控
DWD 分析师 已脱敏(如MD5处理身份证)
ADS 业务端APP 仅开放聚合指标
5. 迭代敏捷性增强
- 隔离变更影响:屏蔽原始数据的异常,避免造成数仓跟着大动作的修改,当ODS层表结构变动时,只需重构DWD→DWS层ETL,ADS层应用无感知
- 快速响应需求:减少重复开发,新业务接入可直接调用DWS层数据,交付周期从周级缩短至小时级
问题二. 数据仓库分层(层级划分),每层做什么?数据分层及定位
1. ODS层(Operational Data Store)
核心职责:原始数据接入与镜像存储
数据形态
与业务库同构(如MySQL表结构直导Hive)
Kafka流数据落地为Parquet文件
典型操作
-- Sqoop同步示例(保留delete标志位)
sqoop import \
--connect jdbc:mysql://db_ip/userdb \
--table orders \
--hive-import \
--hive-table ods.orders \
--delete-target-dir
关键特征
存储T+1全量快照(部分场景保留binlog增量日志)
建立基线分区:dt=yyyy-mm-dd
禁止业务直接访问(通过视图提供受限查询)
2. DWD层(Data Warehouse Detail)
核心职责:标准化清洗与维度融合
核心加工流程
具体任务
1. 数据质量加固
空值填充(如user_id缺失时置为-9999)
枚举值转换(将status_code映射为可读标签)
2. 维度退化
-- 订单事实表+商家维度退化
CREATE TABLE dwd.fact_order AS
SELECT o.order_id,o.amount,s.shop_name, -- 退化维度字段s.city_level
FROM ods.orders o
LEFT JOIN ods.shop s ON o.shop_id = s.id;
3. 敏感数据脱敏(身份证/MD5加密)
3. DWS层(Data Warehouse Summary)
核心职责:面向主题的轻度聚合
设计模式
模型类型 | 适用场景 | 示例 |
---|---|---|
每日聚合 | 高频通用指标 | 日UV/PV、交易总额 |
累积快照 | 多事件周期分析 | 用户生命周期转化漏斗 |
拉链表 | 缓慢变化维度追踪 | 历史价格波动分析 |
--案例
-- 用户日粒度行为摘要表
CREATE TABLE dws.user_action_daily
PARTITIONED BY (dt STRING)
AS
SELECT user_id,COUNT(CASE WHEN event_type='click' THEN 1 END) AS click_cnt,SUM(dwell_time) AS total_duration
FROM dwd.event_detail
WHERE dt='${current_day}'
GROUP BY user_id;
核心价值
预计算减少80%重复聚合开销
统一原子指标口径(如DAU定义全局一致)
4. ADS层(Application Data Service)
核心职责:面向应用的灵活加工
技术特性
支持非范式存储(JSONB/Array列存储复杂结构)
动态冷热分离(近期数据存ClickHouse,历史数据归档至Iceberg)
接口化服务(通过Presto/Trino暴露HTTP API)
分层 | 定位 | 特征 |
ADS 应用数据层 |
|
|
DWS 汇总数据层 |
|
|
DWD 明细数据层 |
|
|
ODS 贴源数据层 |
|
|
问题三. 数仓建模的流程?维度建模的步骤,如何确定这些维度的
一、数仓建模核心流程
- **业务需求锚定** - 深度对齐业务目标(如电商关注「交易转化率」「用户复购周期」) - 识别关键业务过程(订单创建、支付成功、物流签收)。与业务方进行用例风暴(例:电商促销效果分析需追踪「优惠券核销率」「跨品类购买关联」;识别关键指标计算口径(如GMV是否排除取消订单)
概念模型设计(领域模型抽象)
- 划分主题域(用户域、商品域、交易域),划分核心实体(用户、商品、渠道)及事件(浏览、下单、支付)
- 明确跨域数据关联规则(例:用户画像与商品偏好矩阵的连接键)
- 定义实体关系:1:1 / 1:N / M:N(例:用户与收货地址的1:N关系)
逻辑模型构建
- 选择建模方法论:维度建模(Kimball)/ 范式建模(Inmon)
- 制定数据分级存储策略(热温冷数据生命周期)
物理模型实施
- 库表结构定义(分区键、分桶策略、压缩算法)
- 存储引擎选型(Hive/ClickHouse/Doris)
数据管道开发
- ETL链路配置(增量合并策略、幂等性保障)
- 数据质量监控(波动阈值告警、主键唯一性校验)
二、维度建模四步法(Kimball方法论)
STEP 1:选定业务过程
-- 示例:电商核心业务过程 • 用户注册 --> dim_user • 商品曝光 --> fact_impression • 加购行为 --> fact_cart • 订单支付 --> fact_order
STEP 2:声明事实表粒度
- 原子粒度原则:每条记录对应业务最小操作单元
✅ 有效案例:订单事实表以 单个SKU粒度 存储
❌ 错误案例:按小时聚合销量(损失明细追溯能力)
STEP 3:维度拆解
维度类型 | 作用 | 典型字段 |
---|---|---|
退化维度 | 直接嵌入事实表 | 订单编号/物流单号 |
角色扮演维度 | 同一维度多场景复用 | 时间维度(支付/发货时间) |
缓慢变化维度 | 处理渐变属性 | 用户会员等级 |
维度确定方法:
- 业务调研:收集运营常用筛选条件(城市、渠道、品类)
- 现有报表反推:分析BI系统中高频分组字段
- 公共维度总线:跨业务线统一维度定义(如地理维度标准)
STEP 4:事实量化
事实类型 | 计算特性 | 示例 |
---|---|---|
可加事实 | 所有维度均可累加 | 销售额/商品数量 |
半可加事实 | 仅特定维度可聚合 | 账户余额(不可跨时间加) |
不可加事实 | 必须依赖比率计算 | 折扣率/毛利率 |
问题四. 怎么解决hive中的数据倾斜问题?
- 使用Map端聚合:在Hive中,可以通过设置
hive.map.aggr=true
来开启Map端聚合,这样可以在Map阶段进行部分聚合,减少Reduce阶段的数据量。 - 增加Reduce任务数:通过调整
mapred.reduce.tasks
参数(或者Hive中的hive.exec.reducers.bytes.per.reducer
)来增加Reduce任务的数量,使每个Reduce任务处理的数据量更均匀。 - 对倾斜Key进行特殊处理: a. 单独处理倾斜Key:将倾斜的Key单独拿出来处理,然后再和其他数据合并。 b. 随机前缀法:在Group By或Join时,对倾斜的Key添加随机前缀,使得原本一个Key的数据分散到多个Reduce任务中,然后再进行聚合或去前缀处理。
- 使用SMB Join(Sort-Merge-Bucket Join):如果表是分桶且排序的,可以使用SMB Join来避免数据倾斜。
- 使用Skew Join优化:在Hive 0.10.0之后,可以通过设置
hive.optimize.skewjoin=true
来开启Skew Join优化。当某个Key出现倾斜时,Hive会自动将倾斜的Key分到多个Reduce任务中处理。
Hive数据倾斜的本质是分布式哈希的局限性。据实践经验,采用分桶表+自动优化的组合方案可在不改写业务逻辑前提下解决85%倾斜问题,剩余15%需结合业务特性和盐值策略精细化处理。
引擎级优化参数集
-- 核心参数组合(写入hive-site.xml或session级设置)
SET hive.exec.parallel=true; -- 开启任务并发
SET hive.exec.parallel.thread.number=16; -- 并发线程数
SET hive.optimize.skewjoin=true; -- 自动处理Join倾斜
SET hive.skewjoin.key=100000; -- 超过10w条相同key即判定倾斜
SET hive.optimize.skewjoin.compiletime=true; -- 编译时识别倾斜
SET hive.groupby.skewindata=true; -- GroupBy倾斜优化
SET mapred.reduce.tasks=200; -- 动态调整Reduce数量
下面我将针对不同场景给出具体解决方案,包括代码示例。
场景1:GROUP BY 倾斜(如null值占比超40%)
--方案1:两阶段聚合(局部聚合+全局聚合)SELECT key, SUM(cnt) AS total
FROM (
SELECT key, CAST(RAND() * 10 AS INT) AS salt, -- 添加随机盐值 COUNT(1) AS cnt
FROM source_table GROUP BY key, salt -- 第一阶段带盐值聚合
) tmp GROUP BY key; -- 第二阶段去盐值聚合 -- 🟢方案2:自动倾斜处理(Hive 2.3+)SET hive.groupby.skewindata=true; -- 开启自动优化SELECT key, COUNT(1) FROM table GROUP BY key;
场景2:JOIN 倾斜(大卖家关联订单表)
-- 🟢 方案1:MapJoin强制广播小表
SET hive.auto.convert.join=true; -- 开启自动MapJoin
SET hive.mapjoin.smalltable.filesize=25000000; -- 小表阈值25MB/* 手动指定MapJoin */
SELECT /*+ MAPJOIN(small_table) */ large_table.id, small_table.name
FROM large_table
JOIN small_table ON large_table.id = small_table.id;-- 🟢 方案2:分桶Join(需预先分桶)
CREATE TABLE orders_bucketed (order_id BIGINT,seller_id BIGINT
) CLUSTERED BY (seller_id) INTO 128 BUCKETS;-- 启用分桶Join优化
SET hive.optimize.bucketmapjoin=true;
SET hive.optimize.bucketmapjoin.sortedmerge=true;SELECT /*+ MAPJOIN(s) */ o.*, s.seller_name
FROM orders_bucketed o
JOIN seller_bucketed s ON o.seller_id = s.seller_id;
场景3:NULL值倾斜(空key聚集)
-- 🧂 NULL值特殊处理方案
SELECT COALESCE(key, CONCAT('NULL_', CAST(RAND()*100 AS STRING))) AS new_key,value
FROM source_table;-- 最终聚合时还原
SELECT CASE WHEN new_key LIKE 'NULL_%' THEN NULL ELSE new_key END AS orig_key,SUM(value)
FROM tmp_table
GROUP BY new_key;
场景4:动态分区写入倾斜
-- 危险的分区写入
INSERT OVERWRITE TABLE sales PARTITION(dt)
SELECT ..., dt FROM source;-- 修复方案:两阶段写入
SET hive.optimize.sort.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
问题五. 怎么定位hive中的数据倾斜问题?
▶步骤1:抓取慢任务日志
# 获取YARN应用日志
yarn logs -applicationId application_123456789_0001 > app.log# 提取关键统计指标
grep -A20 "Counters:" app.log | grep -E 'RECORDS_OUT|FILE_BYTES_READ'
典型倾斜日志:
Reducer 2 RECORDS_OUT_INTERM: 3,452,178
Reducer 3RECORDS_OUT_INTERM: 128 # ← 异常低值
Reducer 4 RECORDS_OUT_INTERM: 5,892,361 # ← 异常高值
▶ 步骤2:分析执行计划定位高危操作
EXPLAIN EXTENDED
SELECT user_id, count(*)
FROM orders
GROUP BY user_id; -- 风险点:GROUP BY字段-- 查看执行计划中的关键节点
_STAGE PLANS:Reducer 2: # 关注Reducer数量Group By Operator: # 高风险操作位置keys: user_id (type: string)
▶ 步骤3:采样可疑字段分布
-- 快速定位热点Key(替换your_table和key_column)
WITH distribution AS (SELECT key_column, COUNT(1) AS cnt FROM your_table GROUP BY key_column ORDER BY cnt DESC LIMIT 10
)
SELECT key_column,cnt,ROUND(cnt/(SELECT SUM(cnt) FROM distribution),3)*100||'%' AS ratio
FROM distribution;
输出样例:
user_id cnt ratio
null 4508923 41.2% -- 空值倾斜
0 3201654 29.3%
123456 89562 0.8%
▶ 步骤4:动态调试验证
-- 方法1:添加随机前缀打散数据
SELECT /*+ MAPJOIN(b)*/ a.user_id, b.order_info
FROM (SELECT *, concat(user_id, '_', floor(rand()*10)) as join_key FROM users
) a JOIN orders b ON a.join_key = b.join_key;-- 方法2:分桶预处理
CREATE TABLE user_bucketed
CLUSTERED BY(user_id) INTO 100 BUCKETS
AS SELECT * FROM users;