离线数仓基石:Hive与Spark SQL的深度对比,ETL流程设计与分区策略
聊到离线数仓,Hive 和 Spark SQL 是绕不开的两个核心引擎。很多人觉得它们差不多,都是写 SQL 跑批处理。其实不然。我在一线摸爬滚打这些年,见过太多因为选型失误导致凌晨三点还在调任务的案例。今天咱们就把这两兄弟掰开揉碎,看看它们到底差在哪,以及怎么用好它们。
Hive vs Spark SQL:不只是快慢的问题
先给个结论:Hive 是“稳”,Spark SQL 是“快”。但实际选型远没这么简单。
| 对比维度 | Hive (MapReduce/Tez) | Spark SQL |
|---|---|---|
| 执行模型 | MapReduce 多阶段,磁盘落中间结果 | DAG 计算,内存管道,尽量不落盘 |
| 启动开销 | 每个 Job 拉起 JVM,分钟级 | Executor 常驻,秒级 |
| 数据规模 | PB 级稳定,不怕 OOM | TB~PB 级,内存压力大时需调优 |
| SQL 兼容性 | HiveQL,部分函数行为不同 | ANSI SQL 更全,Spark 3.x 进步明显 |
| 小文件处理 | 天然合并机制,但慢 | 需手动 coalesce/repartition |
| 资源弹性 | YARN 调度,可动态分配 | 动态资源分配,但 shuffle 时容易卡住 |
我的经验之谈:日调度任务,数据量在 500GB 以下,我倾向用 Spark SQL。超过 5TB 的复杂 ETL,尤其涉及多表大 Join,我反而会先用 Hive 跑一遍,再用 Spark 做后处理。别问为什么,问就是被 OOM 坑过。
ETL 流程设计:分层是门艺术
离线数仓的 ETL,说白了就是“搬砖+装修”。但砖怎么搬,装修顺序怎么排,直接决定你后面能不能睡个安稳觉。
我习惯把 ETL 分成四个阶段:
- 数据接入(ODS):原封不动拉过来,加个分区字段和业务日期。别做任何清洗,保留现场。
- 数据清洗(DWD):去重、空值处理、格式统一。这里最容易踩坑——我曾经因为一个字段的空格没 trim,导致下游关联全错位。
- 数据整合(DWS):轻度聚合,按主题域建模。比如用户主题、订单主题。宽表在这里诞生。
- 数据应用(ADS):面向报表、BI、接口的最终表。通常很窄,指标明确。
避坑指南:我曾经在 DWD 层直接做全量关联,结果一个 10 亿行的表跑了 6 小时。后来改成增量+拉链表,半小时搞定。记住:能增量就别全量,能分区就别扫全表。
分区策略:选对了,性能翻倍
分区是离线数仓的灵魂。选错了分区键,查询就是全表扫描,跑死你。
我总结了三类常用分区策略:
| 策略 | 适用场景 | 注意事项 |
|---|---|---|
| 时间分区(dt) | 日志、订单、行为数据 | 粒度建议天,避免小时分区导致小文件爆炸 |
| 业务分区(region/city) | 地域性强的数据 | 分区数控制在 100 以内,太多影响元数据性能 |
| 组合分区(dt+type) | 多维度筛选场景 | 层级别超过 3 层,否则 HDFS 目录树太深 |
举个例子,我做过一个电商数仓:
-- 推荐的分区设计
CREATE TABLE dwd_order_detail (
order_id STRING,
user_id STRING,
product_id STRING,
amount DECIMAL(10,2),
status STRING
)
PARTITIONED BY (dt STRING, region STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 查询时利用分区裁剪
SELECT * FROM dwd_order_detail
WHERE dt = '2024-01-15'
AND region = '华东';
注意:分区字段不要用中文,不要用特殊字符。我见过有人用“2024年01月”做分区值,结果 Hive 解析直接报错。老老实实用 yyyy-MM-dd 格式。
Spark SQL 在 ETL 中的实战技巧
用 Spark SQL 做 ETL,有几个坑我替你们踩过了:
- 小文件问题:Spark 默认输出分区数等于最后 stage 的并行度。我习惯在写入前加
.coalesce(n)或.repartition(n),n 根据数据量来,一般 1GB 对应 1 个文件。 - Shuffle 优化:大表 Join 小表,用
broadcast join。我一般手动设置spark.sql.autoBroadcastJoinThreshold=104857600(100MB),超过这个值就考虑分桶。 - 动态分区写入:开启
spark.sql.sources.partitionOverwriteMode=dynamic,避免全表覆盖。这个我吃过亏——一次误操作把历史分区全清了。
-- Spark SQL 动态分区写入示例
INSERT OVERWRITE TABLE dws_user_daily
PARTITION (dt)
SELECT
user_id,
SUM(amount) AS total_amount,
dt
FROM dwd_order_detail
WHERE dt = '2024-01-15'
GROUP BY user_id, dt;
一张图看懂离线数仓核心逻辑
下面这张图是我自己总结的,涵盖了 Hive/Spark SQL 选型、ETL 分层和分区策略的关系。你把它存下来,做架构设计时拿出来对照,基本不会跑偏。
总结一下
离线数仓的基石,说白了就三件事:选对引擎、分好层、定好分区。Hive 和 Spark SQL 不是二选一,而是互补。ETL 分层别贪多,四层足够。分区策略提前规划好,别等数据量上来了再改。
嗯,这些经验都是我用加班和报警堆出来的。你照着做,至少能少踩一半的坑。