数据开发 L2:核心构建
数据开发进阶指南,掌握数仓建模、ETL开发、Spark/Flink基础,构建数据处理的核心能力。
数据开发工程师 L2:核心构建#
[!quote] 写在前面 如果你正在读这篇文档,说明你已经度过了最初的迷茫期,对 Linux、SQL、Python 有了基本的掌握,开始接触真正的”大数据”了。你可能第一次听说”维度建模”、“数仓分层”这些概念,可能第一次写的 Hive SQL 跑了 3 个小时还没出结果,可能第一次遇到”数据倾斜”这个让人头疼的问题。
别担心,这些都是 L2 阶段的必经之路。这篇文档会尽可能真诚地告诉你,这个阶段应该学什么、怎么学,以及如何避开我们踩过的那些坑。
这个阶段的你,可能是这样的#
画像一:SQL 写得不错,但 Hive 跑起来完全不一样#
你在 L1 阶段 SQL 学得挺好,SELECT、JOIN、GROUP BY 都很熟练。但进入大数据环境后,你发现同样的逻辑,在 Hive 里跑起来完全不是那么回事。一个简单的 JOIN,在 MySQL 里秒出结果,在 Hive 里跑了 2 小时还 OOM 了。你开始意识到,分布式计算和单机数据库是两个世界。
给你的建议:你遇到的正是 L2 阶段的核心挑战——理解分布式计算原理。不是 SQL 语法变了,而是底层执行逻辑完全不同了。你需要理解 MapReduce 的基本原理,理解为什么”大表 JOIN 大表”这么慢,理解什么是 Shuffle。这些概念搞清楚,很多问题就迎刃而解了。
画像二:接到需求就开始写代码,但设计总被挑战#
你已经能独立完成开发任务了,速度还挺快。但每次评审,总被架构师或老员工挑战:“为什么这么设计?”、“这个表应该放在 DWD 还是 DWS?”、“这个维度为什么要冗余?“。你发现自己只会”实现”,不会”设计”。
给你的建议:这是好事,说明你已经具备执行力了。L2 阶段的进阶,正是从”能写代码”到”会设计模型”的转变。数仓建模不是随便建几张表,而是有方法论的。维度建模 ↗、数仓分层 ↗这些内容,你需要系统学习。
画像三:任务总出问题,运维让人崩溃#
你负责的几个 ETL 任务,动不动就失败。OOM、数据倾斜、上游延迟、磁盘满了…每天早上第一件事就是看任务有没有跑成功。有时候半夜被电话叫醒处理故障,第二天还要正常上班。你开始怀疑,数据开发是不是就是这么苦。
给你的建议:运维确实是数据开发工作的一部分,但不应该占据你大部分精力。如果你的任务总出问题,往往说明设计有缺陷。比如:没有做好数据量预估、没有处理边界情况、没有设置合理的资源配置。L2 阶段你需要学会”防御性编程”,把问题消灭在开发阶段,而不是让它们在生产环境爆发。
画像四:感觉每天都在写重复的 SQL#
你每天的工作就是:接需求、写 SQL、测试、上线。需求大同小异,SQL 也大同小异。你开始怀疑自己是不是在”搬砖”,这样下去能有成长吗?
给你的建议:这是一个危险信号。如果你发现自己在重复劳动,要么是你的能力已经超越当前工作,要么是你没有深入思考。每写一个 SQL,都可以问自己:这个 SQL 的执行计划是什么?有没有更优的写法?这个指标的口径对吗?业务方真正想要的是什么?带着这些问题工作,“搬砖”也能变成”修炼”。
L2 阶段的核心目标#
用一句话概括:
能够独立设计和构建稳定高效的离线数据仓库。
具体来说:
- 理解维度建模的核心思想,能为一个业务主题设计合理的事实表和维度表
- 掌握数仓分层架构(ODS/DWD/DWS/ADS),知道每一层应该放什么
- 熟练使用 Hive/Spark SQL,能处理常见的性能问题
- 能搭建稳定的 ETL 流水线,任务稳定运行,出问题能快速定位
L1 阶段你学会了”用工具”,L2 阶段你要学会”用方法论”。工具会过时,但方法论是通用的。
必须掌握的核心技能#
1. 维度建模 —— 数仓设计的基石#
如果说数仓开发只能学一个理论,那就是维度建模。这是 Ralph Kimball 在 90 年代提出的方法论,到今天仍然是大多数公司构建数仓的核心方法。
为什么维度建模这么重要?
因为它回答了一个根本问题:如何组织数据,让业务分析变得简单。
传统的关系型建模(3NF)追求数据不冗余,但查询时需要大量 JOIN。维度建模反其道而行之,通过适度冗余换取查询简单。
核心概念:
-
事实表(Fact Table)
- 存储业务过程的度量值(可加、半可加、不可加)
- 通常是最大的表,记录每一笔交易、每一次行为
- 例如:订单事实表记录每一笔订单的金额、数量
-
维度表(Dimension Table)
- 描述业务实体的属性
- 用于”切分”事实,实现多角度分析
- 例如:用户维度表记录用户的年龄、性别、城市
-
星型模型 vs 雪花模型
星型模型(推荐):
维度表
|
维度表 -- 事实表 -- 维度表
|
维度表
雪花模型:
维度子表
|
维度表
|
维度表 -- 事实表 -- 维度表
|
维度表
|
维度子表plaintext实际工作中,星型模型用得更多。雪花模型虽然更规范,但查询时 JOIN 太多,在大数据场景下性能很差。
缓慢变化维(SCD):
这是一个容易被忽视但非常重要的概念。用户今天在北京,明天可能搬到上海。这种变化如何处理?
- Type 1:直接覆盖,不保留历史(最简单,但丢失历史信息)
- Type 2:新增一行,保留历史版本(最常用,通过 start_date/end_date 标识有效期)
- Type 3:增加字段存储历史值(如 current_city, previous_city)
-- Type 2 SCD 示例:查询用户某天的有效信息
SELECT *
FROM dim_user
WHERE user_id = '123'
AND '2024-06-15' >= start_date
AND '2024-06-15' < end_date;sql推荐学习:维度建模基础 ↗ → 逻辑数据建模 ↗ → 物理数据建模 ↗
[!tip] 实战建议 不要只看理论,找一个真实业务场景练习。比如设计一个电商订单主题的数仓模型:订单事实表需要哪些度量?关联哪些维度?用户维度要不要做 SCD?商品维度怎么处理?带着这些问题去设计,你会发现很多”看起来简单”的决策其实很难。
2. 数仓分层架构 —— 让数据有序流动#
刚进入数仓开发的同学,经常会问:为什么要分层?直接从原始数据查不行吗?
当然可以,但当数据量大了、需求多了、人员多了,你会发现:
- 每个人写的口径不一样,同一个指标算出来结果不同
- 修改一个上游表,下游几十个任务全挂了
- 重复计算严重,同一份数据被清洗了无数遍
分层就是为了解决这些问题。
标准分层架构:
数据源 → ODS → DWD → DWS → ADS → 应用
↓ ↓ ↓ ↓
原始层 明细层 汇总层 应用层plaintext各层职责:
| 层级 | 全称 | 职责 | 举例 |
|---|---|---|---|
| ODS | Operational Data Store | 原始数据存储,保持和数据源一致 | ods_order(订单原始表) |
| DWD | Data Warehouse Detail | 明细数据层,清洗、规范化、关联维度 | dwd_order_detail(订单明细表) |
| DWS | Data Warehouse Summary | 汇总数据层,按主题聚合 | dws_user_order_1d(用户日订单汇总) |
| ADS | Application Data Store | 应用数据层,面向具体应用 | ads_daily_sales_report(日销售报表) |
实际工作中的分层细节:
ODS 层:
- 从业务库同步过来的原始数据
- 一般按天分区,保留原始字段
- 只做分区和格式转换,不做业务处理
CREATE TABLE ods_order (
order_id STRING,
user_id STRING,
product_id STRING,
amount DECIMAL(10,2),
create_time STRING,
-- 保留原始字段,不做处理
raw_data STRING
)
PARTITIONED BY (dt STRING)
STORED AS ORC;plaintextDWD 层:
- 数据清洗(去重、去null、格式统一)
- 维度退化(把常用维度冗余进来)
- 业务规则应用(状态码转义、口径统一)
CREATE TABLE dwd_order_detail (
order_id STRING,
user_id STRING,
user_name STRING, -- 冗余用户名称
user_level STRING, -- 冗余用户等级
product_id STRING,
product_name STRING, -- 冗余商品名称
category_name STRING, -- 冗余品类名称
amount DECIMAL(10,2),
order_status STRING, -- 已转义:'待支付'/'已支付'/'已取消'
create_time TIMESTAMP
)
PARTITIONED BY (dt STRING)
STORED AS ORC;plaintextDWS 层:
- 按业务主题聚合
- 常见的聚合粒度:1天(1d)、7天(7d)、30天(30d)、历史累计(td)
CREATE TABLE dws_user_order_1d (
user_id STRING,
order_cnt BIGINT, -- 订单数
order_amount DECIMAL(10,2), -- 订单金额
product_cnt BIGINT, -- 商品数
first_order_time TIMESTAMP, -- 首单时间
last_order_time TIMESTAMP -- 末单时间
)
PARTITIONED BY (dt STRING)
STORED AS ORC;plaintext推荐学习:数据仓库与数据湖建模 ↗ → 数据开发规范 ↗
[!warning] 新手常犯的错误 不要跳层开发。比如直接从 ODS 算 ADS,跳过 DWD 和 DWS。看起来省事,但后果是:
- 口径无法复用,每个需求都要重新清洗数据
- 数据质量无法保证,问题难以追溯
- 计算资源浪费,同样的数据被重复处理
3. Hive/Spark SQL —— 大数据开发的主战场#
L1 阶段你学了 SQL,L2 阶段你要学的是分布式 SQL。语法看起来差不多,但底层完全不同。
为什么同样的 SQL,Hive 跑起来这么慢?
因为 Hive 把 SQL 翻译成 MapReduce(或 Spark)任务,涉及大量的数据 Shuffle。
一个简单的 GROUP BY 背后发生了什么:
SELECT city, COUNT(*)
FROM orders
GROUP BY city;
1. Map 阶段:读取所有数据,按 city 分组
2. Shuffle 阶段:相同 city 的数据发送到同一个 Reducer
3. Reduce 阶段:统计每个 city 的数量
如果 city 分布不均(比如 90% 的订单来自北京),
那 90% 的数据会发送到同一个 Reducer,这就是数据倾斜。plaintext必须掌握的 Hive/Spark 特性:
- 分区表:按时间或业务维度分区,避免全表扫描
-- 创建分区表
CREATE TABLE orders (
order_id STRING,
amount DECIMAL(10,2)
)
PARTITIONED BY (dt STRING, hour STRING)
STORED AS ORC;
-- 查询时指定分区,避免全表扫描
SELECT * FROM orders
WHERE dt = '2024-06-15' AND hour = '10';sql- 桶表:把数据分成固定数量的文件,加速 JOIN
-- 创建桶表
CREATE TABLE orders_bucketed (
order_id STRING,
user_id STRING,
amount DECIMAL(10,2)
)
CLUSTERED BY (user_id) INTO 256 BUCKETS
STORED AS ORC;
-- 两个按相同字段分桶的表 JOIN,效率大幅提升sql- 常用优化参数:
-- 启用 Map 端聚合,减少 Shuffle 数据量
SET hive.map.aggr = true;
-- 启用自动 MapJoin
SET hive.auto.convert.join = true;
SET hive.mapjoin.smalltable.filesize = 25000000;
-- 启用动态分区
SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict;
-- Spark 相关
SET spark.sql.shuffle.partitions = 200;
SET spark.sql.adaptive.enabled = true;sql推荐学习:SQL优化 ↗
4. 数据倾斜处理 —— L2 阶段的必考题#
面试必问,工作必遇。数据倾斜是分布式计算中最常见也最头疼的问题。
什么是数据倾斜?
简单说就是:数据分布不均匀,导致部分节点任务量远超其他节点。
比如一个 GROUP BY 操作,99% 的数据 key 都是 “null”,那所有 null 值都会发送到同一个 Reducer,这个 Reducer 就会特别慢,其他 Reducer 早早完成,都在等它。
如何发现数据倾斜?
- 任务执行时间远超预期
- 大部分 Task 很快完成,个别 Task 跑了很久
- 报 OOM 错误
常见解决方案:
- 处理空值倾斜:
-- 问题 SQL
SELECT a.*, b.*
FROM table_a a
LEFT JOIN table_b b
ON a.user_id = b.user_id;
-- 如果 table_a 有大量 null 的 user_id,会导致倾斜
-- 解决方案:给 null 值加随机数打散
SELECT a.*, b.*
FROM table_a a
LEFT JOIN table_b b
ON COALESCE(a.user_id, CONCAT('null_', RAND())) = b.user_id;sql- 处理热点 Key 倾斜(两阶段聚合):
-- 问题:90% 的订单来自北京
SELECT city, COUNT(*)
FROM orders
GROUP BY city;
-- 解决方案:两阶段聚合
-- 第一阶段:加随机数打散
SELECT city, SUM(cnt) as cnt
FROM (
SELECT
city,
COUNT(*) as cnt
FROM orders
GROUP BY city, CAST(RAND() * 100 AS INT) -- 加随机数
) t
GROUP BY city;sql- MapJoin(小表广播):
-- 如果有一张小表,可以直接广播到所有 Map 端
-- 避免 Shuffle,彻底解决倾斜
-- Hive 写法
SELECT /*+ MAPJOIN(b) */ a.*, b.*
FROM big_table a
JOIN small_table b
ON a.key = b.key;
-- Spark SQL 写法
SELECT /*+ BROADCAST(b) */ a.*, b.*
FROM big_table a
JOIN small_table b
ON a.key = b.key;sql- 倾斜 Key 单独处理:
-- 把倾斜的 Key(如 null、热点城市)单独拿出来处理
-- 然后 UNION ALL 合并结果
-- 正常数据
SELECT city, COUNT(*)
FROM orders
WHERE city != '北京'
GROUP BY city
UNION ALL
-- 倾斜数据单独处理
SELECT '北京' as city, COUNT(*)
FROM orders
WHERE city = '北京';sql[!tip] 面试技巧 面试时被问到数据倾斜,不要只说”加随机数”。最好能说清楚:
- 数据倾斜的原因(数据分布不均)
- 如何发现(监控指标、执行计划)
- 多种解决方案及其适用场景
- 你在实际工作中遇到的案例
5. 任务调度与工程规范 —— 让数据流水线稳定运行#
写出一个正确的 SQL 只是第一步,让它每天稳定运行才是关键。
任务调度系统:
常用的调度系统有 Airflow、DolphinScheduler、Azkaban 等。核心概念都类似:
- DAG(有向无环图):定义任务之间的依赖关系
- 调度周期:天、小时、分钟级别
- 重跑与回溯:任务失败后如何重跑,历史数据如何补录
# Airflow DAG 示例
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime
dag = DAG(
'daily_order_etl',
schedule_interval='0 3 * * *', # 每天凌晨 3 点
start_date=datetime(2024, 1, 1),
)
# 任务定义
ods_task = BashOperator(
task_id='load_ods_order',
bash_command='hive -f /scripts/ods_order.sql',
dag=dag,
)
dwd_task = BashOperator(
task_id='load_dwd_order',
bash_command='hive -f /scripts/dwd_order.sql',
dag=dag,
)
dws_task = BashOperator(
task_id='load_dws_order',
bash_command='hive -f /scripts/dws_order.sql',
dag=dag,
)
# 依赖关系
ods_task >> dwd_task >> dws_taskpython工程规范:
好的代码规范能减少很多麻烦:
-
命名规范:
- 表名:{层级}_{业务域}_{主题}_{粒度},如 dws_trade_order_1d
- 字段名:见名知意,user_id 而非 uid,create_time 而非 ctime
-
SQL 书写规范:
-- 好的 SQL 风格
SELECT
user_id,
COUNT(DISTINCT order_id) AS order_cnt,
SUM(amount) AS total_amount
FROM dwd_order_detail
WHERE dt = '${bizdate}'
AND order_status = 'paid'
GROUP BY user_id
HAVING total_amount > 100;
-- 不好的 SQL 风格
select user_id,count(distinct order_id) order_cnt,sum(amount) total_amount from dwd_order_detail where dt='${bizdate}' and order_status='paid' group by user_id having total_amount>100sql- 幂等性设计:
-- 任务应该支持重跑,重跑结果一致
-- 不好的写法:INSERT INTO(多次运行数据会重复)
INSERT INTO TABLE result_table PARTITION(dt='2024-06-15')
SELECT * FROM source_table;
-- 好的写法:INSERT OVERWRITE(重跑会覆盖)
INSERT OVERWRITE TABLE result_table PARTITION(dt='2024-06-15')
SELECT * FROM source_table;sql6. 关于进阶技能的选择#
L2 阶段,你可能会听到很多”还需要学 XXX”的声音。这里帮你理清优先级。
Java:什么时候必须学?
| 你的工作内容 | Java 是否必要 | 建议 |
|---|---|---|
| 写 Hive/Spark SQL,偶尔写 Python | 不必要 | 继续精进 SQL 和 Python |
| 需要开发 UDF(自定义函数) | 必要 | UDF 主要用 Java 写 |
| 经常遇到 Java 报错需要排查 | 建议学 | 至少能看懂异常栈 |
| 想深入理解 Spark/Flink 原理 | 必须学 | 源码都是 Java/Scala |
[!tip] 务实的建议 大多数 L2 阶段的工作,Python + SQL 足够应付。Java 可以在遇到具体需求(比如要写 UDF)时再学,不必提前焦虑。
Docker:什么程度够用?
L2 阶段 Docker 的价值主要是搭建本地开发环境——用 docker-compose 一键启动 MySQL、Kafka、Hive 等组件,比传统安装方便太多。
你需要掌握的程度:
- 能用
docker run启动单个容器 - 能看懂和修改简单的
docker-compose.yml - 能用
docker logs排查问题
这个程度足够 L2 使用。Kubernetes 等更复杂的内容留到 L3 再考虑。
AI 工具:如何正确使用?
L2 阶段 AI 工具(ChatGPT、Claude、Copilot)可以大幅提升效率:
| 场景 | AI 能帮你 | 但你必须做 |
|---|---|---|
| 复杂 SQL | 生成初版代码 | 检查 JOIN 条件、边界情况、在小数据集验证 |
| 报错排查 | 解释错误含义、给出方向 | 理解根因、验证解决方案 |
| 建模设计 | 提供参考方案 | 结合业务场景做决策 |
| 性能优化 | 分析执行计划 | 验证优化效果 |
[!warning] 关键提醒 AI 不了解你的业务背景和数据特点。AI 生成的 SQL 必须验证,特别是 JOIN 条件和聚合逻辑。把 AI 当顾问,不是当执行者。
你可能会遇到的困难#
”理论学了很多,实际建模还是不会”#
维度建模的书看了,星型模型、雪花模型都知道,但面对真实业务还是不知道怎么下手。
解决方案:找一个真实场景,从头到尾设计一遍。推荐从电商订单开始:
- 梳理业务过程:浏览、加购、下单、支付、发货、收货
- 确定事实表:每个业务过程对应一张事实表
- 确定维度:用户、商品、店铺、时间、地区…
- 确定度量:金额、数量、时长…
- 画出模型图,评审,修改,再评审
”任务老是 OOM”#
这是 L2 阶段最常见的问题之一。
排查步骤:
- 确认是 Driver OOM 还是 Executor OOM
- 检查是否有数据倾斜(看 Task 执行时间分布)
- 检查是否有笛卡尔积(JOIN 条件是否正确)
- 检查数据量是否超出预期
- 根据原因调整:加资源、优化 SQL、处理倾斜
”不知道该学 Hive 还是 Spark”#
答案是都要学,但侧重点不同。
- Hive:语法简单,适合入门,很多公司还在用
- Spark SQL:性能更好,功能更强,是趋势
建议:先用 Hive 理解分布式 SQL 的基本概念,然后转向 Spark SQL。好消息是它们的 SQL 语法几乎一样,迁移成本很低。
“感觉成长很慢”#
L2 阶段是个漫长的过程,可能 1-2 年才能真正毕业。
加速成长的方法:
- 主动承担复杂任务,而不是只做简单需求
- 每个任务都问自己:有没有更好的设计方案?
- 多和架构师、资深同事交流,学习他们的思考方式
- 参与故障复盘,了解问题根因
- 尝试重构一个老模块,这是最好的学习机会
L2 阶段可以胜任的岗位#
完成 L2 阶段的学习后,你可以胜任:
数据开发工程师(中级)
- 主要工作:数仓模型设计与开发、ETL 任务开发与优化
- 薪资参考:一线城市 20-35K,二线城市 15-25K
- 面试重点:维度建模、SQL 优化、数据倾斜处理
数仓工程师
- 主要工作:数仓架构设计、指标体系建设、数据质量保障
- 特点:更偏业务理解和架构设计
大数据开发工程师
- 主要工作:Spark/Flink 应用开发、数据处理 Pipeline 构建
- 特点:更偏技术深度,可能涉及一些框架源码
[!note] 关于跳槽 L2 阶段是跳槽的黄金期。1-3 年经验的数据开发,市场需求量大,薪资涨幅空间也大。但不建议频繁跳槽,最好在一家公司深耕 1.5-2 年,把一个完整的项目从头到尾做一遍,再考虑下一步。简历上”完整负责过一个数仓项目”比”在三家公司各待了半年”有说服力得多。
给 L2 学习者的真诚建议#
1. 深入理解原理,而不只是会用#
Hive SQL 跑得慢,不要只想着”调参数”。去理解它的执行计划,理解 MapReduce 的原理,理解 Shuffle 是怎么回事。搞清楚原理,遇到问题才能快速定位。
2. 培养设计思维#
L2 阶段最重要的转变是从”执行者”变成”设计者”。每接到一个需求,不要立刻开始写 SQL。先想清楚:
- 这个需求的本质是什么?
- 应该放在哪一层?
- 有没有可以复用的表?
- 这个设计能支撑未来的扩展吗?
3. 建立自己的”故障库”#
每次遇到问题,解决后记录下来:问题现象、排查过程、根本原因、解决方案。时间长了,你会发现大部分问题都是”似曾相识”的,解决速度会越来越快。
4. 主动暴露在复杂场景中#
不要只挑简单的活干。主动请缨做那些复杂的、有挑战性的任务。比如:
- 重构一个历史遗留的乱七八糟的模块
- 优化一个跑了 8 小时的慢任务
- 设计一个新业务的数仓模型
这些挑战会让你成长得更快。
接下来#
当你能够熟练设计数仓模型、稳定交付 ETL 任务,开始有这样的困惑时:
- “离线数仓满足不了业务需求,他们要实时数据”
- “PB 级的数据,现有架构已经撑不住了”
- “我想深入了解 Spark 的底层原理,而不只是会用”
- “数据湖、湖仓一体这些新概念,我该怎么跟进?”
恭喜你,你已经准备好进入下一个阶段了。
➡️ L3:架构演进 ↗ —— 实时计算、性能极致优化、数据架构设计
相关资源:
- 维度建模基础 ↗ —— 数仓建模的核心方法论
- 数据仓库与数据湖建模 ↗ —— 分层架构详解
- SQL优化 ↗ —— Hive/Spark SQL 优化技巧
- 数据开发规范 ↗ —— 工程规范最佳实践
- L1:工程启蒙 ↗ —— 如果你基础还不够扎实,可以回顾