深夜十一点,金勺子餐厅的后厨依然灯火通明。总厨老金盯着仓库里堆积如山的食材——五百斤土豆、三百斤白菜、一整头处理好的黄牛——明天中午,他要为全城一万名宾客准备宴席。一个人做不完,一百个人一起做,问题在于怎么分工。老金最后定下的办法是:不按“一道菜”分工,而是按“一道工序”分工。切配组负责把食材处理成半成品并贴上标签,灶台组负责把同类半成品炒成菜,传菜组负责把它们送到指定灶台。三组人各干各的、互不等待,最终拼出一桌万人宴。 这个思路,正是本章主角——MapReduce 编程模型——的灵魂。MapReduce 是 Google 于 2004 年提出的分布式计算框架,它把“海量数据”看作仓库里的食材,把“计算任务”看作后厨的工序:拆开(Split)、加工(Map)、归拢(Shuffle)、合炒(Reduce),四步一气呵成。读完整章,你会发现自己不仅能看懂 MapReduce 的每一行机制,还能把同一套“分而治之”的思维迁移到 Spark、Flink 等现代大数据框架中。
理解 MapReduce,你就握住了大数据批处理的钥匙:无论日后写数据管道还是读分布式源码,这套“分而治之”的思维都会反复为你指路。
目录
可视化演示
展示Map-Shuffle-Reduce三阶段数据流、分布式数据本地执行及Shuffle优化策略,支持步骤切换。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Map-Shuffle-Reduce流水线 输入分片→Map输出KV→Shuffle按Key分组→Reduce聚合输出 map: (k1,v1)→[(k2,v2)], reduce: (k2,[v2])→[(k3,v3)] 三阶段分布式数据处理
核心思想:分而治之的厨房艺术
老金是“金勺子”餐厅的总厨,有一天接到一笔大订单:给全城一万人做宴席。食材堆积如山,光是土豆就有五千斤。老金清楚,一个人哪怕有三头六臂也做不完——但如果是五百个厨师一起做呢?他做了个决定:不按“一道菜”分工,而是按“一道工序”分工。切配的只切配,颠勺的只颠勺,装盘的只装盘,每道工序独立推进,谁也不等谁。
计算机世界的“海量数据”和这座食材山一样,单台机器的计算力根本撬不动。MapReduce 的思想正是老金那句“按工序分工”:把一个大任务拆成成千上万个小任务,让它们在不同机器上同时干,最后再把结果拼起来。这个思想有个响亮的名字——分而治之(Divide and Conquer)。为了让拆出去的小任务能“无缝拼回来”,MapReduce 规定所有数据统一用键值对(key-value)传递,相当于每份食材都贴上“菜名-分量”的标签。
MapReduce 这个名字本身就是两个阶段:Map(映射)负责加工,Reduce(归约)负责汇总。它由 Google 在 2004 年提出,最了不起的地方在于把并行计算中的脏活累活——任务拆分、机器调度、通信、容错——全部收进框架,程序员只需要写两个函数。下面的公式就是这两个函数的“接口合同”:
老金把工序定好,切配、颠勺、装盘各司其职,厨师们甚至不需要知道隔壁灶台在忙什么。MapReduce 也承诺:你只需要关心“食材”和“菜”,剩下的搬运与协调,交给框架这位总厨。
- 输入键值对 $(k_1, v_1)$Map 函数的输入,例如文件里的一行文本,行号是键、内容是值,好比一张写着“食材编号-食材”的入库单。
- 中间输出 $[(k_2, v_2)]$Map 处理后发射的键值对列表,方括号表示“不止一个”,一个输入能产生零个或多个输出,好比一棵白菜被切成好几份,每份都贴上新标签。
- 归约输入 $(k_2, [v_2])$框架自动把所有键为 $k_2$ 的值收集成列表再交给 Reduce,相当于把贴着同一标签的食材从四面八方归拢到一个灶台。
- 最终输出 $[(k_3, v_3)]$Reduce 处理完吐出的结果列表,写入分布式文件系统,就是宴席最终端上桌的那一道道菜。
执行流程:一场四棒接力赛
宴席当天,食材一车车运进后厨。切配主管老陈没有让所有厨师一拥而上,而是把食材按筐分好,每筐大约五十斤,交给一个切配小组。切配小组只负责自己那一筐:削皮、切丝、贴标签。接着,传菜小工把贴着同一标签的食材送到同一个灶台,灶台厨师把一筐筐同类食材炒成一道菜。整个过程像一场接力,每一棒都有明确起点和终点。
一个 MapReduce 作业(Job)的执行流程,就是这场四棒接力。第一棒是输入分片(Input Split):框架把输入文件按大小切成若干分片,每个分片对应一个 Map 任务;第二棒是 Map 阶段:每个任务独立处理自己的分片,把中间结果写到本地磁盘;第三棒是 Shuffle 阶段:框架对 Map 输出的键值对做分区、排序和归并,把相同键的数据送往同一个 Reduce;第四棒是 Reduce 阶段:汇总各组数据,写出最终结果。
分片大小默认与 HDFS 数据块一致(通常 128MB),保证“一个分片通常落在单台机器上”,方便后续做数据本地性调度。Shuffle 里的“分区”决定了某个键值对去哪台 Reduce 机器,最常用的分区策略是哈希取模:
老陈发现,只要标签贴得准,传菜通道再乱也不会串菜。MapReduce 也一样——哈希分区像一张精确的传菜地图,让海量中间数据在集群里“对号入座”。
- 中间键 $k_2$Map 输出的键,比如单词 "apple",它是分区的唯一依据,好比食材标签上的菜品名。
- 哈希散列 $\text{hash}(k_2)$把任意字符串映射成一个整数,让键尽可能均匀地落在各个分区,避免某个 Reduce 任务“撑死”、别的“饿死”。
- 取模 $\bmod R$用哈希值除以 Reduce 任务数 $R$ 取余数,得到 $0$ 到 $R-1$ 之间的分区编号,好比用“菜品名笔画数”决定去几号灶台。
- 分区结果每个键都被分到唯一一个分区,Shuffle 就按这个编号把数据路由过去,保证相同键永远进入同一个 Reduce。
编程接口:后厨的三份岗位说明书
金勺子餐厅想把切配的活标准化,老金写了三份岗位说明书:一份给切配厨师(Mapper),一份给灶台厨师(Reducer),一份给排班经理(Driver)。切配厨师的职责是“把任何输入切成片并贴标签”,灶台厨师的职责是“把同类标签的下锅合炒”,排班经理负责制定菜单、分配人手、启动整个流水线。
在 Hadoop MapReduce 里,这三份说明书对应三个 Java 接口:Mapper 类定义 map() 方法,把输入键值对转换成中间键值对;Reducer 类定义 reduce() 方法,把同一键的值列表聚合;Driver 代码则创建 Job 对象,配置输入输出路径、键值类型、任务数量,然后提交作业。框架约定输入输出格式可以插拔,比如 TextInputFormat 每次读一行,KeyValueTextInputFormat 按分隔符拆键值。
以最经典的“词频统计(WordCount)”为例:map() 把每一行文本按空格拆成单词,每遇到一个词就发射一个 (单词,1);框架自动把相同单词的 1 归拢成一个列表;reduce() 把列表里所有 1 加起来,得到单词总数。map 与 reduce 之间的“数据协议”就是键值对,只要遵守协议,两端可以随便换实现。
老金的岗位说明书越写越细,新来的厨师看一眼就知道自己这步该干什么。MapReduce 的编程接口同样把“做什么”和“怎么做”分离——框架管调度,程序员管逻辑,这是它风靡十余年的根本原因。
- 单词键 $w$reduce 收到的键,例如 "mapreduce",它是归约的单位,好比同一道菜的名字。
- 值集合 $V(w)$Shuffle 归拢后,键为 $w$ 的所有值组成的列表,在词频统计里就是一堆数字 1。
- 累加求和 $\sum$对列表里的所有 $v$ 做加法,把 $n$ 个 1 加出总次数,这是最朴素的归约运算。
- 最终输出得到 $(w, \text{总次数})$,写入 HDFS,词频统计完成。
关键机制:灶台起火了怎么办
万人宴最怕的不是菜难做,而是意外:切配组切到一半灶台起火;传菜小工把整筐食材撒了一地;某个厨师手脚慢,别人炒完三锅他还在第一锅。老金的对策是:每道工序都有备份人手,谁掉链子就立刻换人顶上去;同时规定食材只用本区域仓库的,绝不让厨师为了拿一根葱横穿整个城市。
集群环境里,机器故障是常态而不是意外。MapReduce 的容错核心是“任务级重试”:TaskTracker 通过心跳(Heartbeat)周期性向 JobTracker 汇报状态,超过阈值未上报就被判定为死亡,上面运行的 Map/Reduce 任务会被重新调度到其他节点,默认最多重试 4 次。如果某个任务进度明显落后于同类任务(掉队者),框架会启动“推测执行”(Speculative Execution),在另一台机器上跑一个相同的备份任务,谁先完成就采用谁的结果。
另一个杀手锏是数据本地性(Data Locality)。HDFS 把文件切成块分布在集群中,MapReduce 调度 Map 任务时,优先把它安排到“数据所在的那台机器”,把计算搬到数据旁边,而不是把几百 GB 数据通过网络搬到计算节点。整个作业的耗时可以粗略拆成三段:
老金明白了,一场万人宴拼的不是哪道菜惊艳,而是整个流水线不出大篓子。MapReduce 的容错与本地性设计,正是把“意外”当成预算内开支的后厨管理哲学。
- Map 耗时 $T_{\text{map}}$所有 Map 任务的墙上时间,取决于最慢的那个任务,所以掉队者特别拉低整体效率。
- Shuffle 耗时 $T_{\text{shuffle}}$中间数据排序、分区、网络传输的时间,是常被忽视的隐形瓶颈。
- Reduce 耗时 $T_{\text{reduce}}$归约计算与结果写回 HDFS 的耗时。
- 近似相加三段大体串联,优化任一段都能缩短总时间,这也是 Combiner、压缩等优化策略的出发点。
优化策略:给传菜通道减肥
宴席开席前一小时,传菜通道堵了。切配组切出的几千份“土豆丝”要在短短几分钟内全部传到 3 号灶台,小工们抱着筐子来回跑,走廊水泄不通。老陈想了个办法:每个切配组在传菜前,先把自家筐里的“土豆丝”按份数合个总——5 号组送来的不再是三十份土豆丝,而是一张纸条“土豆丝 30 份”。走廊立刻空了。
这个办法在 MapReduce 里叫 Combiner(预聚合)。它跑在 Map 端,在数据进入 Shuffle 之前先做一次“本地归约”,把同一 Map 任务里相同键的值先合并。在词频统计中,一个 Map 任务里 "mapreduce" 出现了 1000 次,不经过 Combiner 就要传 1000 个 (mapreduce, 1),经过 Combiner 只需传 1 个 (mapreduce, 1000),Shuffle 的网络压力骤减。Combiner 之所以安全,是因为归约满足交换律与结合律:
除了 Combiner,还有两味常用“调味料”。一是自定义 Partitioner:默认哈希分区可能让数据分布不均,可以换用更均匀的分区策略,让 Reduce 任务负载均衡;二是中间数据压缩:Map 输出用 Snappy、LZ4 等高速压缩算法压一压再传,CPU 多花一点,网络和磁盘却省一大截。参数调优也很关键,调整并发任务数、JVM 复用、缓冲区大小,都能让作业吞吐明显提升。
老陈的纸条策略传遍后厨,传菜通道再没堵过。MapReduce 的优化哲学与之相同:传输通道是稀缺资源,与其拓宽走廊,不如减少要传的东西——把计算尽量往数据边上推,把数据尽量压缩了再上路。
- 值集合 $V_m$第 $m$ 个 Map 任务中键 $k$ 对应的值集合,相当于第 $m$ 个切配组筐里的食材。
- 并集 $\bigcup_m$把不同 Map 任务的结果合并到一起,对应 Shuffle 阶段把所有相同键的数据归拢。
- 预归约 $\text{combine}(V_m)$在 Map 端先把本地集合算出一道中间结果,比如把 1000 个 1 先加出 1000。
- 等号的含义因为加法(及其他大多数归约)不在乎“先加谁后加谁”,所以预归约不改变最终答案——这就是 Combiner 合法性的根基。
应用场景:不止于数单词
宴席散场,老金打开账本:这场万人宴用掉了 5000 斤土豆、3000 斤白菜、800 只鸡,按菜品统计消耗、按供应商统计采购、按口味统计满意度……老金发现,只要把“词频统计”里的“词”换成“土豆”“白菜”,同一套流水线能回答无数问题。
MapReduce 的应用远不止数单词。经典场景包括:词频统计(WordCount)——统计全网文档的关键词分布;倒排索引(Inverted Index)——构建“词 → 文档列表”的映射,是搜索引擎的核心数据结构;日志分析——每天数十 TB 的服务器日志,按小时统计各接口的 PV、UV、错误码占比;数据清洗(ETL)——把多来源、乱格式的数据统一解析、去重、标准化后写入数据仓库。这些任务的共同点是:数据量巨大、处理逻辑简单、可并行拆解。
老金把流水线手册沉淀下来,发现“切配-传菜-灶台”几乎能套用到任何大型活动。MapReduce 的意义也在于此:它不只是一套具体代码,而是“大规模数据如何化整为零、再化零为整”的通用范式。理解了它,再学 Spark、Flink 都会事半功倍。
- 词项 $t$被索引的单词或分词单元,比如“分布式”,它是查询的入口。
- 文档记录 $(d_i, f_i)$第 $i$ 篇文档的编号 $d_i$,以及词项在该文档中出现的次数 $f_i$。
- 集合意义一个词项对应一个“出现在哪些文档、各出现多少次”的列表,Map 负责产出记录,Reduce 负责按文档合并。
- 检索价值用户搜索时先查词项再读文档列表,从“扫描所有文档”变成“查一张小表”,速度天差地别。
AI 应用场景
场景 1:海量日志分析与错误聚合
阶段:数据预处理 / 批处理管道
解决的问题:传统方式靠单机 grep 或脚本扫描日志,TB 级数据要跑几天才能出结果,且内存易爆。MapReduce 把日志按时间段分片,Map 并行解析每一行、提取接口名与状态码,Reduce 按“接口+状态码”聚合成计数,小时级搞定全量分析。这就像后厨把整座食材山分成一筐筐,切配组各管一筐。
具体案例:某电商大促当天产生 30 TB 访问日志,数据工程师编写 MapReduce 作业统计每个接口的 2xx/4xx/5xx 占比,发现某商品详情页接口 5xx 异常飙高,及时告警修复。
场景 2:搜索引擎倒排索引构建
阶段:特征构建 / 索引数据管道
解决的问题:全网网页数以百亿计,单机内存无法承载“词→文档”映射。MapReduce 的 Map 阶段对每篇网页分词并发射 (词,文档ID),Shuffle 自动把同词聚到一起,Reduce 按文档 ID 排序合并成倒排链,最终写回分布式存储供检索服务读取。
具体案例:搜索引擎团队定期用 MapReduce 批处理重建索引库,每天将新增爬取页面合并进索引,检索延迟稳定在几十毫秒。
场景 3:多源数据清洗与标准化(ETL)
阶段:数据治理 / ETL
解决的问题:多业务线导出的订单、用户、商品数据格式混杂,存在重复、缺失、单位不统一。MapReduce 在 Map 阶段做字段解析与格式转换,Reduce 阶段按键去重合并,输出统一 schema 的数据仓库表,为后续 AI 特征工程提供干净原料。
具体案例:某金融科技公司用 MapReduce 清洗几十个来源的交易流水,统一币种与时间戳格式,去重后落库。
Python 演示
"""Simulate MapReduce word count in a single process."""from collections import defaultdict def map_line(line): """Map phase: split a line into (word, 1) pairs.""" words = line.strip().lower().split() return [(w, 1) for w in words] def reduce_word(word, values): """Reduce phase: sum all counts of a word.""" return (word, sum(values)) def map_reduce(texts, num_reducers=2): """Run the full pipeline: Map -> Shuffle -> Reduce.""" mapped = [] for line in texts: mapped.extend(map_line(line)) groups = defaultdict(list) for word, count in mapped: partition = hash(word) % num_reducers groups[(word, partition)].append(count) results = [] for (word, _), values in sorted(groups.items()): results.append(reduce_word(word, values)) return sorted(results) if __name__ == "__main__": lines = [ "hello world mapreduce", "hello distributed computing", "mapreduce is powerful", "world of mapreduce", ] for word, count in map_reduce(lines): print(f"{word}: {count}")
mapped.extend(map_line(line))把所有 Map 任务的输出汇入同一个列表,模拟框架收集中间结果。hash(word) % num_reducers复现正文的哈希分区公式,让不同单词尽量均匀分布到不同“灶台”。groups[(word, partition)].append(count)按键和分区号双重分组,演示“相同键进入同一分区”的 Shuffle 语义。sum(values)Reduce 端求和,对应词频统计的归约运算。if __name__ == "__main__":提供可执行入口,直接运行即可观察完整流程。
常见误区
误区MapReduce 既然能处理 TB 级数据,那实时查询、机器学习训练、流式计算应该都能往里塞,只要数据量大就行。
正解MapReduce 是为“高吞吐离线批处理”设计的。每个作业都要经历磁盘落盘、网络 Shuffle、再次落盘,延迟以分钟计;需要几十轮迭代的算法会在反复读写磁盘中慢如蜗牛。实时与迭代场景应选择 Flink、Spark 等专门引擎。就像万人宴的流水线再快,也没法给单桌客人做现点现炒。
为何常见:只看到“分布式 = 快”的宣传,忽略了批处理与流处理、迭代计算之间的根本差异。
误区Map 任务都跑在集群上,它们应该能像多线程一样共享变量、随时交换中间结果,这样协作更高效。
正解经典 MapReduce 采用“无共享架构”,Map 任务完全独立、互不通信,唯一的跨节点数据流动发生在 Map 与 Reduce 之间的 Shuffle 屏障。若 Map 间直接通信,就需要引入锁、消息队列等机制,复杂度剧增,与“分而治之”的初衷相悖。
为何常见:把多线程编程的经验直接搬过来,没意识到分布式环境下“通信即风险”。
误区Combiner 类签名和 Reducer 一样,直接把 reduce 逻辑原样搬过去当 Combiner 用,反正都是归约。
正解Combiner 是在 Map 端做的局部预归约,必须满足交换律与结合律,而且输入输出类型必须一致。求和、取最大值这类运算可以;但如果 reduce 逻辑依赖全局信息(比如求中位数、去重后的 Top-N),预归约会得到错误结果。
为何常见:看到 Combiner 继承 Reducer 就以为二者等价,忽略了“局部合并是否改变全局结果”这一层数学约束。
误区集群几千台机器闲着可惜,把输入切成几百万个极小分片,每个分片一个任务,并行度拉满肯定最快。
正解每个分片对应一个 Map 任务,而每个任务都有启动、调度、初始化的固定开销。分片过细时,任务数远超可用槽位,调度器忙于排队和元数据管理,整体吞吐反而下降。分片大小默认对齐 HDFS 块(128MB)是一个经过权衡的经验值。
为何常见:线性思维,以为并行度与性能恒成正比,忽视了任务调度的固定成本。
误区流程图里 Map → Shuffle → Reduce 是严格串行的三个阶段,前一阶段不结束,后一阶段绝不能开始。
正解Hadoop 的 Shuffle 其实细分为“复制、排序、归约”三个子阶段:Reduce 端会在 Map 任务陆续完成时提前拉取已完成的 Map 输出(复制阶段),但归约函数要等所有 Map 完成、数据排序归并完毕后才启动。所以准确说法是“复制提前、归约等待”。
为何常见:把教材里的阶段图当成严格的时序图,忽略了框架内部的流水线优化。
面试题
Q1. MapReduce 最核心的设计思想是什么?
- A.用缓存加速数据访问
- B.分而治之并行计算← 正确答案
- C.用 SQL 简化查询逻辑
- D.内存计算提升速度
答案:B。MapReduce 的本质是把巨大问题拆成无数独立小任务并行执行,再汇总结果,即“分而治之”。缓存、SQL 化、内存计算都是其他框架的优化手段,不是 MapReduce 的核心思想。就像万人宴不可能靠一位厨师加一口大锅完成,必须拆给整个后厨同时做。
Q2. Map 与 Reduce 之间传递数据的统一格式是什么?
- A.关系型数据库表
- B.JSON 嵌套文档
- C.键值对记录← 正确答案
- D.向量化数组
答案:C。MapReduce 规定 Map 输出与 Reduce 输入均为 (键,值) 键值对,这是框架的“通用接口契约”。数据库表、JSON、数组都是具体数据形态,不是框架规定的传递格式。就像切配组传给灶台的每份食材都必须贴有“菜名-分量”标签。
Q3. Shuffle 阶段最核心的职责是什么?
- A.对输入数据随机打乱顺序
- B.按键排序分组并归并传输← 正确答案
- C.压缩中间结果到本地磁盘
- D.把最终结果写回分布式文件
答案:B。Shuffle 负责把 Map 输出按键分区、排序、归并,再把相同键的数据路由到同一个 Reduce。随机打乱是字面误导;压缩只是优化手段;写回文件是 Reduce 之后的输出阶段。它好比传菜系统按标签把食材分门别类送到对应灶台。
Q4. 关于数据本地性(Data Locality),正确的说法是什么?
- A.优先把计算调度到数据所在节点← 正确答案
- B.先把全部数据复制到每个计算节点
- C.计算节点强制访问远程数据
- D.数据只在本地磁盘保留一份备份
答案:A。数据本地性是“计算向数据移动”:把 Map 任务调度到持有该分片副本的机器上,避免海量数据跨网络传输。全量复制成本过高;强制远程访问与本地性背道而驰;副本策略是 HDFS 的独立机制。就像厨师只取本区域仓库的食材,而不是为了一根葱横穿全城。
Q5. Combiner 能安全生效的前提条件是什么?
- A.归约函数满足交换律与结合律← 正确答案
- B.输入数据必须是纯文本格式
- C.Reduce 输出必须写入 HDFS
- D.Map 任务的数量必须为偶数
答案:A。Combiner 在 Map 端做局部预归约,只有当归约满足交换律与结合律(如求和、取最大值)时,局部合并才不会改变全局结果。数据格式、输出位置、任务数量都与 Combiner 的合法性无关。好比切配组先把 30 份土豆丝合计成“30 份”,到达灶台后总数不变。
Q6. Partitioner 的作用是决定什么?
- A.输入数据在磁盘上的分块大小
- B.每条键值对进入哪个 Reducer← 正确答案
- C.每个 Map 任务分到的输入分片
- D.整个作业的调度优先级
答案:B。Partitioner 根据键计算分区号,决定中间键值对交给哪个 Reduce 任务,默认实现是哈希取模。分块大小由文件系统决定;输入分片由 InputFormat 决定;调度优先级属于资源管理器的职责。它好比传菜地图上写明的“这道菜去几号灶台”。
Q7. 某 Map 任务进度远远落后于同类任务,成为“掉队者”,框架通常会怎么做?
- A.原地等待,不做任何干预
- B.在另一节点启动相同任务备份← 正确答案
- C.动态缩小该任务的输入分片
- D.强行提高该任务的优先级
答案:B。推测执行(Speculative Execution)会在另一台机器为掉队任务启动一个备份任务,两者竞争,谁先完成就用谁的结果。等待会拖慢整个作业;分片在启动时就已固定;优先级调整作用有限。就像老金看到 3 号厨师太慢,立刻让 7 号厨师也炒同一道菜。
Q8. 下列哪些属于 MapReduce 的典型适用场景?(选两项)
- A.离线批量的日志统计← 正确答案
- B.毫秒级的在线查询
- C.海量文档的倒排索引← 正确答案
- D.交互式的即席分析
答案:AC。MapReduce 适合高吞吐离线批处理,日志统计与倒排索引构建都是典型应用。毫秒级在线查询需要低延迟,应选 HBase、Redis 等;即席分析强调交互响应,更适合 OLAP 引擎。就像万人宴的大锅菜手艺,做不了单桌的现点现炒。
Q9. 关于输入分片与任务并行度,下列说法正确的有哪些?(选两项)
- A.分片大小默认与 HDFS 块一致← 正确答案
- B.分片越细性能一定线性提升
- C.分片过多会引入调度开销← 正确答案
- D.每个分片会启动多个 Map 任务
答案:AC。分片默认对齐 HDFS 块(128MB)以便数据本地性调度;分片过细会导致任务调度、元数据开销过大,性能不升反降。并行度与性能并非线性关系;一个分片对应恰好一个 Map 任务。就像切菜筐分得太碎,搬运筐子的工夫比切菜还多。
Q10. 关于 Shuffle 与 Reduce 的时序,下列说法正确的有哪些?(选两项)
- A.Reduce 可提前拉取已完成 Map 输出← 正确答案
- B.Reduce 归约函数须等所有 Map 完成← 正确答案
- C.Map 输出必须首先写入 HDFS
- D.Shuffle 全程只发生在本地磁盘
答案:AB。Hadoop 的 Shuffle 复制阶段会在 Map 陆续完成时提前传数据,但归约函数要等所有 Map 完成、数据排序归并后才开始。Map 输出写的是本地磁盘而非 HDFS;Shuffle 涉及跨节点网络传输。这对应后厨“食材先陆续送到灶台边,但灶台要等备齐才开火”。