拾穗数据

Back

数据开发工程师 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。维度建模反其道而行之,通过适度冗余换取查询简单。

核心概念

  1. 事实表(Fact Table)

    • 存储业务过程的度量值(可加、半可加、不可加)
    • 通常是最大的表,记录每一笔交易、每一次行为
    • 例如:订单事实表记录每一笔订单的金额、数量
  2. 维度表(Dimension Table)

    • 描述业务实体的属性
    • 用于”切分”事实,实现多角度分析
    • 例如:用户维度表记录用户的年龄、性别、城市
  3. 星型模型 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

各层职责

层级全称职责举例
ODSOperational Data Store原始数据存储,保持和数据源一致ods_order(订单原始表)
DWDData Warehouse Detail明细数据层,清洗、规范化、关联维度dwd_order_detail(订单明细表)
DWSData Warehouse Summary汇总数据层,按主题聚合dws_user_order_1d(用户日订单汇总)
ADSApplication 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;
plaintext
DWD 层:
- 数据清洗(去重、去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;
plaintext
DWS 层:
- 按业务主题聚合
- 常见的聚合粒度: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。看起来省事,但后果是:

  1. 口径无法复用,每个需求都要重新清洗数据
  2. 数据质量无法保证,问题难以追溯
  3. 计算资源浪费,同样的数据被重复处理

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 特性

  1. 分区表:按时间或业务维度分区,避免全表扫描
-- 创建分区表
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
  1. 桶表:把数据分成固定数量的文件,加速 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
  1. 常用优化参数
-- 启用 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 早早完成,都在等它。

如何发现数据倾斜?

  1. 任务执行时间远超预期
  2. 大部分 Task 很快完成,个别 Task 跑了很久
  3. 报 OOM 错误

常见解决方案

  1. 处理空值倾斜
-- 问题 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
  1. 处理热点 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
  1. 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
  1. 倾斜 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] 面试技巧 面试时被问到数据倾斜,不要只说”加随机数”。最好能说清楚:

  1. 数据倾斜的原因(数据分布不均)
  2. 如何发现(监控指标、执行计划)
  3. 多种解决方案及其适用场景
  4. 你在实际工作中遇到的案例

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_task
python

工程规范

好的代码规范能减少很多麻烦:

  1. 命名规范

    • 表名:{层级}_{业务域}_{主题}_{粒度},如 dws_trade_order_1d
    • 字段名:见名知意,user_id 而非 uid,create_time 而非 ctime
  2. 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>100
sql
  1. 幂等性设计
-- 任务应该支持重跑,重跑结果一致
-- 不好的写法: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;
sql

推荐学习数据开发规范数据开发测试

6. 关于进阶技能的选择#

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 当顾问,不是当执行者。


你可能会遇到的困难#

”理论学了很多,实际建模还是不会”#

维度建模的书看了,星型模型、雪花模型都知道,但面对真实业务还是不知道怎么下手。

解决方案:找一个真实场景,从头到尾设计一遍。推荐从电商订单开始:

  1. 梳理业务过程:浏览、加购、下单、支付、发货、收货
  2. 确定事实表:每个业务过程对应一张事实表
  3. 确定维度:用户、商品、店铺、时间、地区…
  4. 确定度量:金额、数量、时长…
  5. 画出模型图,评审,修改,再评审

”任务老是 OOM”#

这是 L2 阶段最常见的问题之一。

排查步骤

  1. 确认是 Driver OOM 还是 Executor OOM
  2. 检查是否有数据倾斜(看 Task 执行时间分布)
  3. 检查是否有笛卡尔积(JOIN 条件是否正确)
  4. 检查数据量是否超出预期
  5. 根据原因调整:加资源、优化 SQL、处理倾斜

”不知道该学 Hive 还是 Spark”#

答案是都要学,但侧重点不同。

  • Hive:语法简单,适合入门,很多公司还在用
  • Spark SQL:性能更好,功能更强,是趋势

建议:先用 Hive 理解分布式 SQL 的基本概念,然后转向 Spark SQL。好消息是它们的 SQL 语法几乎一样,迁移成本很低。

“感觉成长很慢”#

L2 阶段是个漫长的过程,可能 1-2 年才能真正毕业。

加速成长的方法

  1. 主动承担复杂任务,而不是只做简单需求
  2. 每个任务都问自己:有没有更好的设计方案?
  3. 多和架构师、资深同事交流,学习他们的思考方式
  4. 参与故障复盘,了解问题根因
  5. 尝试重构一个老模块,这是最好的学习机会

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:架构演进 —— 实时计算、性能极致优化、数据架构设计


相关资源

数据开发 L2:核心构建
https://blog.ss-data.cc/blog/data-engineer-l2-core
Author 石头
Published at 2025年1月5日
Comment seems to stuck. Try to refresh?✨