深夜十一点,金勺子餐厅的后厨依然灯火通明。总厨老金盯着仓库里堆积如山的食材——五百斤土豆、三百斤白菜、一整头处理好的黄牛——明天中午,他要为全城一万名宾客准备宴席。一个人做不完,一百个人一起做,问题在于怎么分工。老金最后定下的办法是:不按“一道菜”分工,而是按“一道工序”分工。切配组负责把食材处理成半成品并贴上标签,灶台组负责把同类半成品炒成菜,传菜组负责把它们送到指定灶台。三组人各干各的、互不等待,最终拼出一桌万人宴。 这个思路,正是本章主角——MapReduce 编程模型——的灵魂。MapReduce 是 Google 于 2004 年提出的分布式计算框架,它把“海量数据”看作仓库里的食材,把“计算任务”看作后厨的工序:拆开(Split)、加工(Map)、归拢(Shuffle)、合炒(Reduce),四步一气呵成。读完整章,你会发现自己不仅能看懂 MapReduce 的每一行机制,还能把同一套“分而治之”的思维迁移到 Spark、Flink 等现代大数据框架中。

岗位关联

理解 MapReduce,你就握住了大数据批处理的钥匙:无论日后写数据管道还是读分布式源码,这套“分而治之”的思维都会反复为你指路。

目录
  1. 可视化演示
  2. 核心思想:分而治之的厨房艺术
  3. 执行流程:一场四棒接力赛
  4. 编程接口:后厨的三份岗位说明书
  5. 关键机制:灶台起火了怎么办
  6. 优化策略:给传菜通道减肥
  7. 应用场景:不止于数单词
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示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 也承诺:你只需要关心“食材”和“菜”,剩下的搬运与协调,交给框架这位总厨。

Map(k1,v1)→[(k2,v2)]Reduce(k2,[v2])→[(k3,v3)]\text{Map}(k_1, v_1) \rightarrow [(k_2, v_2)] \qquad \text{Reduce}(k_2, [v_2]) \rightarrow [(k_3, v_3)]
公式说明:Map 吃进一个键值对,吐出一串键值对;Reduce 吃进一个键和一堆值,最终吐出一串键值对。全局的计算,就是无数个这样的小变换。
  1. 输入键值对 $(k_1, v_1)$Map 函数的输入,例如文件里的一行文本,行号是键、内容是值,好比一张写着“食材编号-食材”的入库单。
    (k1,v1)(k_1, v_1)
  2. 中间输出 $[(k_2, v_2)]$Map 处理后发射的键值对列表,方括号表示“不止一个”,一个输入能产生零个或多个输出,好比一棵白菜被切成好几份,每份都贴上新标签。
    [(k2,v2)][(k_2, v_2)]
  3. 归约输入 $(k_2, [v_2])$框架自动把所有键为 $k_2$ 的值收集成列表再交给 Reduce,相当于把贴着同一标签的食材从四面八方归拢到一个灶台。
    (k2,[v2])(k_2, [v_2])
  4. 最终输出 $[(k_3, v_3)]$Reduce 处理完吐出的结果列表,写入分布式文件系统,就是宴席最终端上桌的那一道道菜。
    [(k3,v3)][(k_3, v_3)]

执行流程:一场四棒接力赛

宴席当天,食材一车车运进后厨。切配主管老陈没有让所有厨师一拥而上,而是把食材按筐分好,每筐大约五十斤,交给一个切配小组。切配小组只负责自己那一筐:削皮、切丝、贴标签。接着,传菜小工把贴着同一标签的食材送到同一个灶台,灶台厨师把一筐筐同类食材炒成一道菜。整个过程像一场接力,每一棒都有明确起点和终点。

一个 MapReduce 作业(Job)的执行流程,就是这场四棒接力。第一棒是输入分片(Input Split):框架把输入文件按大小切成若干分片,每个分片对应一个 Map 任务;第二棒是 Map 阶段:每个任务独立处理自己的分片,把中间结果写到本地磁盘;第三棒是 Shuffle 阶段:框架对 Map 输出的键值对做分区、排序和归并,把相同键的数据送往同一个 Reduce;第四棒是 Reduce 阶段:汇总各组数据,写出最终结果。

分片大小默认与 HDFS 数据块一致(通常 128MB),保证“一个分片通常落在单台机器上”,方便后续做数据本地性调度。Shuffle 里的“分区”决定了某个键值对去哪台 Reduce 机器,最常用的分区策略是哈希取模:

老陈发现,只要标签贴得准,传菜通道再乱也不会串菜。MapReduce 也一样——哈希分区像一张精确的传菜地图,让海量中间数据在集群里“对号入座”。

Partition(k2)=hash(k2) mod R\text{Partition}(k_2) = \text{hash}(k_2) \bmod R
公式说明:把每个键用哈希函数均匀散开,再对 Reduce 任务数取模,结果就是它该去的“灶台编号”。
  1. 中间键 $k_2$Map 输出的键,比如单词 "apple",它是分区的唯一依据,好比食材标签上的菜品名。
    k2k_2
  2. 哈希散列 $\text{hash}(k_2)$把任意字符串映射成一个整数,让键尽可能均匀地落在各个分区,避免某个 Reduce 任务“撑死”、别的“饿死”。
    hash(k2)\text{hash}(k_2)
  3. 取模 $\bmod R$用哈希值除以 Reduce 任务数 $R$ 取余数,得到 $0$ 到 $R-1$ 之间的分区编号,好比用“菜品名笔画数”决定去几号灶台。
     mod R\bmod R
  4. 分区结果每个键都被分到唯一一个分区,Shuffle 就按这个编号把数据路由过去,保证相同键永远进入同一个 Reduce。
    Partition(k2)∈{0,1,…,R−1}\text{Partition}(k_2) \in \{0, 1, \dots, R-1\}

编程接口:后厨的三份岗位说明书

金勺子餐厅想把切配的活标准化,老金写了三份岗位说明书:一份给切配厨师(Mapper),一份给灶台厨师(Reducer),一份给排班经理(Driver)。切配厨师的职责是“把任何输入切成片并贴标签”,灶台厨师的职责是“把同类标签的下锅合炒”,排班经理负责制定菜单、分配人手、启动整个流水线。

在 Hadoop MapReduce 里,这三份说明书对应三个 Java 接口:Mapper 类定义 map() 方法,把输入键值对转换成中间键值对;Reducer 类定义 reduce() 方法,把同一键的值列表聚合;Driver 代码则创建 Job 对象,配置输入输出路径、键值类型、任务数量,然后提交作业。框架约定输入输出格式可以插拔,比如 TextInputFormat 每次读一行,KeyValueTextInputFormat 按分隔符拆键值。

以最经典的“词频统计(WordCount)”为例:map() 把每一行文本按空格拆成单词,每遇到一个词就发射一个 (单词,1);框架自动把相同单词的 1 归拢成一个列表;reduce() 把列表里所有 1 加起来,得到单词总数。map 与 reduce 之间的“数据协议”就是键值对,只要遵守协议,两端可以随便换实现。

老金的岗位说明书越写越细,新来的厨师看一眼就知道自己这步该干什么。MapReduce 的编程接口同样把“做什么”和“怎么做”分离——框架管调度,程序员管逻辑,这是它风靡十余年的根本原因。

output(w)=∑v∈V(w)v\text{output}(w) = \sum_{v \in V(w)} v
公式说明:单词 $w$ 对应的所有计数 $v$ 之和,就是这个单词在全文中出现的次数。
  1. 单词键 $w$reduce 收到的键,例如 "mapreduce",它是归约的单位,好比同一道菜的名字。
    ww
  2. 值集合 $V(w)$Shuffle 归拢后,键为 $w$ 的所有值组成的列表,在词频统计里就是一堆数字 1。
    V(w)={v1,v2,…,vn}V(w) = \{v_1, v_2, \dots, v_n\}
  3. 累加求和 $\sum$对列表里的所有 $v$ 做加法,把 $n$ 个 1 加出总次数,这是最朴素的归约运算。
    ∑v∈V(w)v=v1+v2+⋯+vn\sum_{v \in V(w)} v = v_1 + v_2 + \dots + v_n
  4. 最终输出得到 $(w, \text{总次数})$,写入 HDFS,词频统计完成。
    output(w)\text{output}(w)

关键机制:灶台起火了怎么办

万人宴最怕的不是菜难做,而是意外:切配组切到一半灶台起火;传菜小工把整筐食材撒了一地;某个厨师手脚慢,别人炒完三锅他还在第一锅。老金的对策是:每道工序都有备份人手,谁掉链子就立刻换人顶上去;同时规定食材只用本区域仓库的,绝不让厨师为了拿一根葱横穿整个城市。

集群环境里,机器故障是常态而不是意外。MapReduce 的容错核心是“任务级重试”:TaskTracker 通过心跳(Heartbeat)周期性向 JobTracker 汇报状态,超过阈值未上报就被判定为死亡,上面运行的 Map/Reduce 任务会被重新调度到其他节点,默认最多重试 4 次。如果某个任务进度明显落后于同类任务(掉队者),框架会启动“推测执行”(Speculative Execution),在另一台机器上跑一个相同的备份任务,谁先完成就采用谁的结果。

另一个杀手锏是数据本地性(Data Locality)。HDFS 把文件切成块分布在集群中,MapReduce 调度 Map 任务时,优先把它安排到“数据所在的那台机器”,把计算搬到数据旁边,而不是把几百 GB 数据通过网络搬到计算节点。整个作业的耗时可以粗略拆成三段:

老金明白了,一场万人宴拼的不是哪道菜惊艳,而是整个流水线不出大篓子。MapReduce 的容错与本地性设计,正是把“意外”当成预算内开支的后厨管理哲学。

Tjob≈Tmap+Tshuffle+TreduceT_{\text{job}} \approx T_{\text{map}} + T_{\text{shuffle}} + T_{\text{reduce}}
公式说明:作业总耗时约等于 Map、Shuffle、Reduce 三段耗时之和;优化任何一个环节,都能直接缩短总时间。
  1. Map 耗时 $T_{\text{map}}$所有 Map 任务的墙上时间,取决于最慢的那个任务,所以掉队者特别拉低整体效率。
    TmapT_{\text{map}}
  2. Shuffle 耗时 $T_{\text{shuffle}}$中间数据排序、分区、网络传输的时间,是常被忽视的隐形瓶颈。
    TshuffleT_{\text{shuffle}}
  3. Reduce 耗时 $T_{\text{reduce}}$归约计算与结果写回 HDFS 的耗时。
    TreduceT_{\text{reduce}}
  4. 近似相加三段大体串联,优化任一段都能缩短总时间,这也是 Combiner、压缩等优化策略的出发点。
    Tjob≈Tmap+Tshuffle+TreduceT_{\text{job}} \approx T_{\text{map}} + T_{\text{shuffle}} + T_{\text{reduce}}

优化策略:给传菜通道减肥

宴席开席前一小时,传菜通道堵了。切配组切出的几千份“土豆丝”要在短短几分钟内全部传到 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 的优化哲学与之相同:传输通道是稀缺资源,与其拓宽走廊,不如减少要传的东西——把计算尽量往数据边上推,把数据尽量压缩了再上路。

Reduce(k,⋃mVm)=Reduce(k,⋃mcombine(Vm))\text{Reduce}\Big(k, \bigcup_{m} V_m\Big) = \text{Reduce}\Big(k, \bigcup_{m} \text{combine}(V_m)\Big)
公式说明:先把每个 Map 的结果各自“预归约”再汇总,与直接汇总再归约,最终结果完全一致。
  1. 值集合 $V_m$第 $m$ 个 Map 任务中键 $k$ 对应的值集合,相当于第 $m$ 个切配组筐里的食材。
    VmV_m
  2. 并集 $\bigcup_m$把不同 Map 任务的结果合并到一起,对应 Shuffle 阶段把所有相同键的数据归拢。
    ⋃mVm\bigcup_m V_m
  3. 预归约 $\text{combine}(V_m)$在 Map 端先把本地集合算出一道中间结果,比如把 1000 个 1 先加出 1000。
    combine(Vm)\text{combine}(V_m)
  4. 等号的含义因为加法(及其他大多数归约)不在乎“先加谁后加谁”,所以预归约不改变最终答案——这就是 Combiner 合法性的根基。
    Reduce(k,⋃mcombine(Vm))\text{Reduce}\Big(k, \bigcup_{m} \text{combine}(V_m)\Big)

应用场景:不止于数单词

宴席散场,老金打开账本:这场万人宴用掉了 5000 斤土豆、3000 斤白菜、800 只鸡,按菜品统计消耗、按供应商统计采购、按口味统计满意度……老金发现,只要把“词频统计”里的“词”换成“土豆”“白菜”,同一套流水线能回答无数问题。

MapReduce 的应用远不止数单词。经典场景包括:词频统计(WordCount)——统计全网文档的关键词分布;倒排索引(Inverted Index)——构建“词 → 文档列表”的映射,是搜索引擎的核心数据结构;日志分析——每天数十 TB 的服务器日志,按小时统计各接口的 PV、UV、错误码占比;数据清洗(ETL)——把多来源、乱格式的数据统一解析、去重、标准化后写入数据仓库。这些任务的共同点是:数据量巨大、处理逻辑简单、可并行拆解。

老金把流水线手册沉淀下来,发现“切配-传菜-灶台”几乎能套用到任何大型活动。MapReduce 的意义也在于此:它不只是一套具体代码,而是“大规模数据如何化整为零、再化零为整”的通用范式。理解了它,再学 Spark、Flink 都会事半功倍。

Index(t)={(d1,f1),(d2,f2),…,(dn,fn)}\text{Index}(t) = \{(d_1, f_1), (d_2, f_2), \dots, (d_n, f_n)\}
公式说明:词项 $t$ 的倒排索引,是一串“文档编号-出现次数”的记录,查询时直接命中,不用扫全库。
  1. 词项 $t$被索引的单词或分词单元,比如“分布式”,它是查询的入口。
    tt
  2. 文档记录 $(d_i, f_i)$第 $i$ 篇文档的编号 $d_i$,以及词项在该文档中出现的次数 $f_i$。
    (d1,f1),(d2,f2),…(d_1, f_1), (d_2, f_2), \dots
  3. 集合意义一个词项对应一个“出现在哪些文档、各出现多少次”的列表,Map 负责产出记录,Reduce 负责按文档合并。
    Index(t)\text{Index}(t)
  4. 检索价值用户搜索时先查词项再读文档列表,从“扫描所有文档”变成“查一张小表”,速度天差地别。
    {(di,fi)}\{(d_i, f_i)\}

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 演示

python41 行
"""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}")
目的:用纯 Python 单进程模拟 MapReduce 的三段式流程——Map 发射键值对,Shuffle 分组归拢,Reduce 聚合输出。运行后打印每个单词的出现次数。
  1. mapped.extend(map_line(line))把所有 Map 任务的输出汇入同一个列表,模拟框架收集中间结果。
  2. hash(word) % num_reducers复现正文的哈希分区公式,让不同单词尽量均匀分布到不同“灶台”。
  3. groups[(word, partition)].append(count)按键和分区号双重分组,演示“相同键进入同一分区”的 Shuffle 语义。
  4. sum(values)Reduce 端求和,对应词频统计的归约运算。
  5. 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 最核心的设计思想是什么?

单选题 · easy

  • A.用缓存加速数据访问
  • B.分而治之并行计算← 正确答案
  • C.用 SQL 简化查询逻辑
  • D.内存计算提升速度

答案:B。MapReduce 的本质是把巨大问题拆成无数独立小任务并行执行,再汇总结果,即“分而治之”。缓存、SQL 化、内存计算都是其他框架的优化手段,不是 MapReduce 的核心思想。就像万人宴不可能靠一位厨师加一口大锅完成,必须拆给整个后厨同时做。

Q2. Map 与 Reduce 之间传递数据的统一格式是什么?

单选题 · easy

  • A.关系型数据库表
  • B.JSON 嵌套文档
  • C.键值对记录← 正确答案
  • D.向量化数组

答案:C。MapReduce 规定 Map 输出与 Reduce 输入均为 (键,值) 键值对,这是框架的“通用接口契约”。数据库表、JSON、数组都是具体数据形态,不是框架规定的传递格式。就像切配组传给灶台的每份食材都必须贴有“菜名-分量”标签。

Q3. Shuffle 阶段最核心的职责是什么?

单选题 · mid

  • A.对输入数据随机打乱顺序
  • B.按键排序分组并归并传输← 正确答案
  • C.压缩中间结果到本地磁盘
  • D.把最终结果写回分布式文件

答案:B。Shuffle 负责把 Map 输出按键分区、排序、归并,再把相同键的数据路由到同一个 Reduce。随机打乱是字面误导;压缩只是优化手段;写回文件是 Reduce 之后的输出阶段。它好比传菜系统按标签把食材分门别类送到对应灶台。

Q4. 关于数据本地性(Data Locality),正确的说法是什么?

单选题 · mid

  • A.优先把计算调度到数据所在节点← 正确答案
  • B.先把全部数据复制到每个计算节点
  • C.计算节点强制访问远程数据
  • D.数据只在本地磁盘保留一份备份

答案:A。数据本地性是“计算向数据移动”:把 Map 任务调度到持有该分片副本的机器上,避免海量数据跨网络传输。全量复制成本过高;强制远程访问与本地性背道而驰;副本策略是 HDFS 的独立机制。就像厨师只取本区域仓库的食材,而不是为了一根葱横穿全城。

Q5. Combiner 能安全生效的前提条件是什么?

单选题 · mid

  • A.归约函数满足交换律与结合律← 正确答案
  • B.输入数据必须是纯文本格式
  • C.Reduce 输出必须写入 HDFS
  • D.Map 任务的数量必须为偶数

答案:A。Combiner 在 Map 端做局部预归约,只有当归约满足交换律与结合律(如求和、取最大值)时,局部合并才不会改变全局结果。数据格式、输出位置、任务数量都与 Combiner 的合法性无关。好比切配组先把 30 份土豆丝合计成“30 份”,到达灶台后总数不变。

Q6. Partitioner 的作用是决定什么?

单选题 · mid

  • A.输入数据在磁盘上的分块大小
  • B.每条键值对进入哪个 Reducer← 正确答案
  • C.每个 Map 任务分到的输入分片
  • D.整个作业的调度优先级

答案:B。Partitioner 根据键计算分区号,决定中间键值对交给哪个 Reduce 任务,默认实现是哈希取模。分块大小由文件系统决定;输入分片由 InputFormat 决定;调度优先级属于资源管理器的职责。它好比传菜地图上写明的“这道菜去几号灶台”。

Q7. 某 Map 任务进度远远落后于同类任务,成为“掉队者”,框架通常会怎么做?

单选题 · hard

  • A.原地等待,不做任何干预
  • B.在另一节点启动相同任务备份← 正确答案
  • C.动态缩小该任务的输入分片
  • D.强行提高该任务的优先级

答案:B。推测执行(Speculative Execution)会在另一台机器为掉队任务启动一个备份任务,两者竞争,谁先完成就用谁的结果。等待会拖慢整个作业;分片在启动时就已固定;优先级调整作用有限。就像老金看到 3 号厨师太慢,立刻让 7 号厨师也炒同一道菜。

Q8. 下列哪些属于 MapReduce 的典型适用场景?(选两项)

多选题 · mid

  • A.离线批量的日志统计← 正确答案
  • B.毫秒级的在线查询
  • C.海量文档的倒排索引← 正确答案
  • D.交互式的即席分析

答案:AC。MapReduce 适合高吞吐离线批处理,日志统计与倒排索引构建都是典型应用。毫秒级在线查询需要低延迟,应选 HBase、Redis 等;即席分析强调交互响应,更适合 OLAP 引擎。就像万人宴的大锅菜手艺,做不了单桌的现点现炒。

Q9. 关于输入分片与任务并行度,下列说法正确的有哪些?(选两项)

多选题 · hard

  • A.分片大小默认与 HDFS 块一致← 正确答案
  • B.分片越细性能一定线性提升
  • C.分片过多会引入调度开销← 正确答案
  • D.每个分片会启动多个 Map 任务

答案:AC。分片默认对齐 HDFS 块(128MB)以便数据本地性调度;分片过细会导致任务调度、元数据开销过大,性能不升反降。并行度与性能并非线性关系;一个分片对应恰好一个 Map 任务。就像切菜筐分得太碎,搬运筐子的工夫比切菜还多。

Q10. 关于 Shuffle 与 Reduce 的时序,下列说法正确的有哪些?(选两项)

多选题 · hard

  • A.Reduce 可提前拉取已完成 Map 输出← 正确答案
  • B.Reduce 归约函数须等所有 Map 完成← 正确答案
  • C.Map 输出必须首先写入 HDFS
  • D.Shuffle 全程只发生在本地磁盘

答案:AB。Hadoop 的 Shuffle 复制阶段会在 Map 陆续完成时提前传数据,但归约函数要等所有 Map 完成、数据排序归并后才开始。Map 输出写的是本地磁盘而非 HDFS;Shuffle 涉及跨节点网络传输。这对应后厨“食材先陆续送到灶台边,但灶台要等备齐才开火”。