61.1 Hadoop 生态

一句话

Hadoop 生态不是 “一个软件”,而是四层分工:HDFS 负责存、YARN 负责调度、MapReduce/Spark 负责算、Hive/HBase 负责 “SQL 与随机读写”;记住分层,比记住组件名有用得多。

四层分工

层 组件 职责 现代替代
存储 HDFS 大文件、多副本、顺序读 对象存储(S3/OSS)
调度 YARN 集群资源分配、队列 K8s + 调度器
计算 MapReduce / Spark / Flink 批/流计算 Spark、Flink
接口 Hive(SQL)、HBase(KV/随机读写)、Presto/Trino(联邦查询) 把存储+计算包装成可用接口 湖仓一体(Iceberg/Hudi + Trino/Spark)

一句话关系:Hive 把 SQL 翻译成 MapReduce/Spark 作业,YARN 给它分资源,HDFS 存中间结果。所以 “Hive 慢” 往往不是 Hive 的问题,而是 “翻译出来的作业 shuffle 太多”。

实测:一个 wordcount 的 IO 放大

100MB 输入,按典型 MapReduce 路径估算:

阶段 字节
读输入 100MB
map 输出 120MB
溢写(spill) 120MB
合并(merge)读写 180MB
shuffle 传输 120MB
reduce 读入 120MB
reduce 输出 10MB
合计 ≈770MB(7.7× 输入)

这解释了 “为什么批处理要尽量减少 shuffle 与落盘”:一个最简单的 wordcount 就能把 IO 放大 7–8 倍,而真实作业往往有多个 stage,放大更严重。

优化方向(按收益排序):
1. 减少 shuffle(map 端预聚合、分区裁剪、广播小表)
2. 减少落盘(Spark 的 pipeline、避免中间结果物化)
3. 提高压缩(shuffle 文件压缩,见 44.4 压缩与编码)

代码:MapReduce 的三段结构

// map:拆词 → (word, 1)
map(k1, v1) → for word in split(v1): emit(word, 1)
// shuffle/sort:按 key 分组(框架完成,代价最大的一步)
// reduce:累加
reduce(k2, [1,1,1...]) → emit(k2, sum)

Spark 里同样的事只需一行:sc.textFile(...).flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)——但底层的 shuffle 与落盘一点没少,只是写法短了。

反模式

反模式 后果
把 Hadoop 当 “一个数据库” 用 点查延迟高,随机写基本不可用
小文件直接进 HDFS NameNode 内存爆(见 10.5 分布式文件系统)
用 Hive 跑高并发小查询 每次启动作业,延迟分钟级
不做分区/分桶 全表扫描,分区裁剪失效
所有作业共用一个队列 大作业堵死小作业
只扩计算不扩存储带宽 shuffle 成为瓶颈,加机器无效

三条铁律

🔑 HDFS 为大文件顺序读设计;小文件与随机写要换存储。

🔑 Hive 慢通常是 “翻译出的作业差”,不是 Hive 本身。

🔑 批处理优化的第一优先级是 “减少 shuffle 与落盘”。

自检

Q1 为什么 “Hive 查询慢” 常常要去看 MapReduce/Spark 的作业,而不是调 Hive 参数?

▸ 答案:因为 Hive 只是把 SQL 翻译成作业,真正的时间花在作业的 shuffle 与落盘上。调 Hive 参数(如并行度、内存)只能在既定执行计划上做微调;如果 SQL 写法导致全表扫描或大 shuffle,参数怎么调都救不回来。

Q2 小文件为什么是 HDFS 的 “死穴”?

▸ 答案:因为每个文件/目录/块都在 NameNode 内存里占一条记录(约 150 字节量级):1 亿小文件 ≈ 15GB 堆,NameNode 会被打爆。而且每个小文件都对应一次 map 任务,调度开销远超数据处理本身。

Q3 YARN 与 K8s 在 “大数据” 场景的分工怎么理解?

▸ 答案:两者都是资源调度层,区别在生态:YARN 为 “长时间运行的大数据作业” 设计(队列、预留、数据本地性),K8s 为 “容器化微服务” 设计。趋势是 “Spark on K8s”,用 K8s 的统一调度替代 YARN,代价是要自己解决数据本地性与队列隔离。

Q4 为什么 “数据本地性” 在 Hadoop 里很重要?

▸ 答案:因为搬数据比搬计算贵:把计算调度到数据所在节点,可以避免跨网络读 HDFS 块。Hadoop 会尽量把 map 任务分配到 “数据所在节点/机架”,本地性差时性能可能下降数倍。计算存储分离(对象存储)后,本地性消失,靠 “高带宽 + 缓存” 弥补。

Q5 HBase 与 HDFS 的关系是什么?

▸ 答案:HBase 把数据存在 HDFS 上:它自己提供 “按 rowkey 随机读写 + 列族”,底层用 HDFS 做持久化与副本。所以 HBase 的写要先写 WAL(HDFS 上的日志)再写 MemStore,读要查 BlockCache 与 HFile——延迟远高于 “纯内存 KV”。

Q6 为什么 “计算存储分离” 会成为趋势?

▸ 答案:因为弹性:存储独立扩展(对象存储近乎无限),计算按需启停(按量付费),不用 “存算按同一比例扩容”。代价是失去数据本地性、依赖高带宽网络,且对象存储的 list/rename 语义弱,需要新的表格式(Iceberg/Hudi)来补。

Q7 一个批处理作业从 30 分钟变 3 小时,先查什么?

▸ 答案:先看输入数据量是否变了(分区/文件数暴涨)、再看shuffle 量(是否数据倾斜,见 61.4 数据倾斜与调优)、最后看资源(队列排队、被抢占)。三步能覆盖 80% 的 “突然变慢”:数据、倾斜、资源。

关联