
说实话,只要在大数据岗位干过一年以上,应该都遇到过那种离谱的 Hive 查询:昨天 3 分钟能跑完的任务,今天突然 40 分钟还卡在 map 阶段;同一个 SQL 在测试环境飞快,到了生产连日志都刷不动;有时候 Tez 跑得稀碎,一切换回 MR 又灵了…,瞬间迷茫了。
我见过最典型的三种“性能突然下降”的现场,几乎每家公司都出现过:
某次排查生产任务,我看到 map 阶段卡了 7 分钟才开始处理数据。看日志一看,全是:
INFO mapreduce.JobSubmitter: number of splits: 1342013420 个 splits?好家伙,这是在用 Hive 群发邮件吗?
结果一看 HDFS 文件,整整 1.8 亿个小文件。NameNode 的 CPU 直接干到 280%,GC 开始上天,整个任务调度都慢得要死。
性能下降不是 SQL 老化,而是小文件越来越多。
另一次,有个指标表从 3 分钟跑成 25 分钟。我盯着执行计划看了半天,发现是一个字段加了 GROUP BY,结果 reduce 全堆在一个节点上,数据倾斜严重得离谱:
还有就是某些任务因为业务增长,数据量几个月翻几倍,但 SQL 完全没改,突然就开始狂 shuffle,网络带宽被榨干,任务能从晚上跑到天亮。
所以 Hive 性能下降并不是“程序变慢了”,而是“你看不见的东西改变了”。往往问题是:文件变多了、数据分布变了、表结构变旧了、底层引擎换了。
我以前也天真以为 Tez 比 MR 快,Spark 比 Tez 快,这是自然规律。直到我遇到一次线上事故,让我改观。
结果某些任务跑得更慢了,而且慢得很均衡,不像 Tez 会卡在 DAG 的某个结点。后来才知道:
不同引擎不是谁更牛,而是“谁更适合你的数据特征”。
场景 | MR | Tez | Spark |
|---|---|---|---|
小并发、大表批处理 | 稳 | 快 | 可能更快 |
多 SQL 组合查询 | 很慢 | 快 | 更快 |
表结构复杂、宽表 | 稳得可怕 | 中等 | OOM 风险大 |
shuffle 超大(>TB) | 最稳 | 可能失败 | 很可能失败 |
资源紧张的集群 | 能跑就行 | 吃资源 | 最吃资源 |
我最后得到一条朴素规律:
Hive on Spark 不是性能升级,而是资源升级。
你资源够多自然快,资源不够也只能干着急。
我第一次被“大表 join 大表”坑,是一个 400GB 的用户行为表和一个 700GB 的日志表。开发同学写了个 SQL:
SELECT a.uid, a.action, b.ip
FROM big_a a
JOIN big_b b
ON a.uid = b.uid;看上去很正常对吧?但这会导致:
结果:
后来改成:
一般的原则是:
如果两边都大,就不要直接 join,要么缩一边,要么分桶,要么提前按 key 聚合到更小再 join。
数据倾斜不常常体现在 code,而是体现在业务数据的不均匀分布。
业务为了标记某些特殊用户,加了下面的代码:
WHERE user_type = '0'问题是 user_type = '0' 的数据量是另外几个类型的 30 倍。结果 map 阶段看起来正常,但 reduce(比如 join 或 group by)就一下子被拉爆。我之前遇到一个任务,map 处理 6 分钟,reduce 处理了 53 分钟,其中一个 reduce 占了全部数据的 88%。
尤其是分母为零、空值、NULL 等。解决方式一般是:
但真正的解决方式是:
去问业务数仓的人,为什么表里 80% 的用户都是 user_type='0'?
技术优化有时候不如改业务字段来得有效。
我特别喜欢 /*+ MAPJOIN(b) */ 这个提示,一键让小表不参与 shuffle,直接广播出去给所有 map 任务。
但很多人不知道,Hive 本身有 auto join 优化,它有阈值,比如:
hive.auto.convert.join=true
hive.mapjoin.smalltable.filesize=25000000默认 25MB 以下的小表会自动被广播。但问题来了:
我们有个看着很小的 dimension 表(500MB),但因为每个分区几十 MB,自动 join 无法命中,导致它一直 shuffle。后来我把它变成一张 unpartitioned 表,直接变成 一个 500MB 的 ORC,反而速度快多了。
所以广播小表有效,但前提是:
真实有效的 SQL:
SELECT /*+ MAPJOIN(dim) */ a.uid, dim.city
FROM dwd_user_log a
LEFT JOIN dim_user_city dim
ON a.uid = dim.uid;有一次我遇到一个非常魔幻的表:30TB 的数据,所有分区都是 textfile,每个表格式都是这种:
字段1\t字段2\t字段3\t...Hive 执行 plan 里根本没有谓词下推、列裁剪,map 完全要扫全表。我们把它改成 ORC 后:
为什么 ORC 这么重要?
通俗点说:
一次线上排查,一个任务突然从 5 分钟跑成 40 分钟。查日志发现它在 full scan,partition pushdown 根本没生效。后面查原因是开发写成了:
WHERE dt = cast('2025-02-11' as string)dt 是 string,你为什么要 cast?Hive 看到 cast,直接放弃分区裁剪。简单一句改成:
WHERE dt = '2025-02-11'任务瞬间回到 4 分钟。有些开发写 WHERE like '%2025%',更离谱。分区条件必须:
打个比方,如果你要在仓库里找一把扳手,合理的方式不是“把整个仓库搬到办公室”,而是“只把工具箱拿来”。列裁剪就是只读必要列。谓词下推就是尽可能提前过滤。
一个任务从:
SELECT * FROM big_table WHERE event_type = 'login'改成:
SELECT uid, event_time FROM big_table WHERE event_type = 'login'IO 从 280GB → 12GB,map 阶段从 18 分钟 → 1 分钟
我以前也会随便写:
set mapreduce.job.reduces=300;结果把集群资源打爆,其他任务都排队。真实经验是:
所以我总结了以下使用规律:
但这取决于你的 key 分布。map 数量不用太操心,HDFS splits 决定的,你要调的是“不要有太多小文件”。另外我常用的几个参数:
set hive.exec.reducers.bytes.per.reducer=512000000; -- 每个 reduce 处理 512MB
set hive.optimize.skewjoin=true; -- 自动倾斜处理
set hive.groupby.skewindata=true; -- group by 倾斜优化
set hive.exec.dynamic.partition.mode=nonstrict;我曾经遇到一个 400GB 的 gzip 文件,解压用了 17 分钟,map 阶段就 17 分钟。换成 snappy 后,同样条件 3~4 分钟搞定。
格式 | 压缩比 | 解压速度 | 适用场景 |
|---|---|---|---|
snappy | 中 | 快到离谱 | Hive 主力格式 |
gzip | 高 | 慢 | 离线冷数据、归档数据 |
lzo | 中 | 中 | 历史遗留项目 |
我们公司有个任务,QPS 正常,Hive 任务也正常,但 NameNode 每天凌晨 CPU 都打满。最后定位到一个日志任务,每天生成 2000 万个小文件。NameNode 的问题是:
我见过 NameNode 因为小文件太多,重启耗时 47 分钟才恢复。
有一次,一个实时 T+1 的指标任务,从稳定 3 分钟跑完 → 突然 30 分钟。当时对于这个问题的的排查步骤如下:
结果看到 join 阶段 shuffle 量从几百 MB → 20GB。
发现 dim_city 表从 200MB → 6GB。开发加了几个 from_app、from_channel 字段,变胖了。
没有命中,因为太大了。
改 SQL:
SELECT /*+ MAPJOIN(dim) */ a.uid, dim.city
FROM dwd_user_log a
LEFT JOIN dim_city dim
ON a.city_id = dim.city_id;变成 3 分钟。
让 dim_city 只保留需要的字段,删掉无关字段后变回 250MB。
从默认的 20 个 reduce → 8 个,避免小文件爆炸。最终任务从 30 分钟回到 3 分钟。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。