一、先把角色放到舞台上
阶段输入输出核心职责生活比喻Map一行行原始记录
二、举个例子:
输入文件内容:
hello world
hello java
Map 输出(中间结果):
(hello, 1)
(world, 1)
(hello, 1)
(java, 1)
Reduce 输入(按 key 分组):
hello: [1, 1]
world: [1]
java: [1]
Reduce 输出(最终结果):
hello 2
world 1
java 1
三、Reduce 的“高光时刻”——必须上 Reduce 的场景
业务特征例子原因需要全局聚合PV、UV、销售额总计同一 key 散落在不同节点,必须二次汇总需要排序输出每天 Top10 热搜Reduce 阶段自带按 key 排序(可配)需要多源连接订单表 join 用户表Map 阶段只能局部 join,全局需 Reduce需要去重计数独立访客、唯一设备同一 ID 可能散在不同 map,需全局去重
一句话:只要 key 的“全集”需要被重新洗牌到同一节点,就离不开 Reduce。
四、可以省掉 Reduce 的场合——Only-Map 任务
把 job.setNumReduceTasks(0)(或 job.setReducerClass(Reducer.class) 干脆不写),框架会跳过 Shuffle & Reduce,Map 直接写 HDFS。
适用场景:
纯过滤 / 转换
例:日志脱敏、字段截取、格式转换——每条记录独立处理,不依赖其他记录。
局部聚合就能满足
例:Combiner 已把
下游自己再聚合
例:Map 先打成 1000 个小文件,下游 Spark 或 ClickHouse 再并行汇总,把二次聚合职责推给下游引擎。
数据量极小,Shuffle 反而亏本
例:配置文件解析,输出几千行,Reduce 启动开销比计算还大。
代码模板:
job.setMapperClass(MyMapper.class);
job.setNumReduceTasks(0); // 关键一句
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
五、Combiner——“本地小 Reduce”不能代替全局 Reduce
很多人把 Combiner 当成“省 Reduce”的银弹,其实它只能减少网络 IO,无法替代跨节点的全局聚合。
Combiner 运行在 Map 端 JVM,输出仍是
所以:
计算可结合 & 可交换(sum、max、min)→ 加 Combiner 提速
求中位数、去重计数(精确)→ Combiner 无效,必须走 Reduce
六、面试快问快答
Q1:WordCount 可以把 Reduce 省掉吗?
答:如果只想看“每个 map 文件里的词频”,可以;但通常要全局词频,就必须 Reduce。
Q2:设置 0 个 Reduce 后,输出文件长啥样?
答:Map 直接输出到 HDFS,文件名保持 part-m-00000,不再出现 part-r-xxx。
Q3:0 Reduce 还能用 Combiner 吗?
答:不能!Combiner 是 Map → Reduce 之间的优化,没有 Reduce 阶段也就不会调用 Combiner。
七、一句话收个尾
Map 负责“分类”,Reduce 负责“汇总”;
当业务只需要“分类”而无需“汇总”时,放心把 Reduce 踢掉,让数据从 Map 端直飞磁盘,省时又省带宽。