Hadoop中的MapReduce学习 - Mapper和shuffle阶段

MapReduce主要分为三个阶段

  • mapper
  • shuffle
  • reduce

MAPPER阶段

以下是 Mapper 阶段的详细工作流程(以 Hadoop 2.x/3.x 的 YARN 架构为例):

1. 输入分片(Input Split)
  • 问题: 如何将超大文件(如 1TB 日志)分发给多个 Mapper 并行处理?

  • 解决方案:

    • InputFormat(如 TextInputFormat)将文件逻辑切分为 分片(Split) (如 128MB / 片)。
    • 关键点: 分片是逻辑划分(不实际切割文件),记录每个分片的文件偏移量和长度。
  • 协同机制:

    ResourceManager 根据分片数量动态分配 Mapper 任务到不同 NodeManager。

2. RecordReader 读取数据
  • 问题: 如何从分片中逐条读取记录?

  • 解决方案:

    • RecordReader(如 LineRecordReader)将分片解析为一条条 <Key, Value> (如 <行偏移量, 行内容>)。
    • 示例:
      输入 "192.168.1.1 - - [01/Jan/2023:00:00:01] GET /home" → 输出 <0, "192.168.1.1 ...">
  • 技术细节:

    通过 FileInputStream 按偏移量读取 HDFS 块,避免全文件加载。

3. Map 函数处理
  • 问题: 如何执行用户自定义逻辑?

  • 解决方案:

    开发者实现 Mapper.map() 方法,对每条记录生成 中间键值对 。
    示例(统计单词):

    scss 复制代码
    // 输入:<行偏移量, "hello world">
    map(LongWritable key, Text value, Context context) {
      String[] words = value.toString().split(" ");
      for (String word : words) {
        context.write(new Text(word), new IntWritable(1)); // 输出:<"hello", 1>, <"world", 1>
      }
    }
4. 环形缓冲区(Circular Buffer)
  • 问题: Map 输出如何暂存?直接写磁盘效率低。

  • 解决方案:

    • 内存中开辟 环形缓冲区(默认 100MB)。

    • Map 输出先写入缓冲区,包含:

      • Key/Value 数据
      • 分区信息(Partition,决定数据归属哪个 Reducer)
      • 元数据(Key/Value 的起始位置、长度)
  • 技术细节:

    使用序列化(如 Writable 接口)减少内存占用。

5. 溢写(Spill)
  • 问题: 缓冲区满了怎么办?

  • 解决方案:

    • 当缓冲区使用率 > 阈值(默认 80%)时,启动 后台溢写线程。

    • 溢写流程:

      1. 分区排序: 按分区分区内按 Key 排序(快速排序)
      2. Combiner 优化(可选): 本地聚合(如合并相同 Key 的 Value)
      3. 写磁盘: 生成临时文件(spill1.out, spill2.out...)
  • 关键点:

    溢写与 Map 处理 并行执行,避免阻塞。

6. 归并合并(Merge)
  • 问题: 多个溢写文件如何整合?

  • 解决方案:

    • Map 任务结束时,将所有溢写文件 归并排序 为一个大文件。

    • 归并策略:

      • 多路归并(默认一次合并 10 个文件)
      • 保持分区内 Key 有序(为 Reduce 阶段做准备)
  • 输出文件:

    最终生成一个 分区且排序 的文件(如 map_out_0.index + map_out_0.data)。


核心问题及解决策略

核心问题 解决方案 技术实现
数据倾斜 自定义分区器(Partitioner) 重写 getPartition() 分散热点 Key
内存溢出 环形缓冲区 + 溢写机制 阈值触发异步磁盘写入
中间数据过大 Combiner 本地聚合 在 Map 端预合并相同 Key 的数据
分片不均 自定义 InputFormat 重写 getSplits() 控制分片逻辑
容错机制 Task 重试机制 NodeManager 监控 Task,失败后重新调度

shuffle

核心目标是 高效、可靠地将 Map 的输出数据分组、排序并传输给对应的 Reduce 任务。

一、Shuffle 阶段的核心目标(本质)

  1. 分组 (Grouping) :将 Map 任务输出的键值对,按照 Key 的哈希值 (或自定义分区器)分配到不同的 分区 (Partition) 中。每个 Partition 对应一个 Reduce 任务【分区数量由reduce数量决定,有8个reduce就有8个partition】。

  2. 排序 (Sorting) :确保发送给同一个 Reduce 任务的所有数据(即同一个 Partition 内),是 按键 (Key) 排序 的。

  3. 数据传输 (Transfer) :将每个 Map 任务【map任务数量由文件类型和】输出的多个 Partition 数据,高效、可靠地传输到各个运行 Reduce 任务的节点上。

    map任务数量由输入的数据类型和大小决定。例子 :如果你有1个1GB的文件存储在HDFS上,而HDFS的块大小设置为128MB,那么Hadoop会尝试创建 1024MB / 128MB = 8 个InputSplit,从而启动8个Map任务】

  4. 合并 (Optional Merge) :在 Reduce 端拉取到来自多个 Map 任务的对应 Partition 数据后,进行合并排序,形成最终的、完全排序的输入流。


二、Shuffle 阶段的技术实现细节(分 Map 端和 Reduce 端)

(一) Map 端 Shuffle
一句话总结:
  1. 流入数据处理:数据进入mapper阶段的缓冲区之前,会先通过hash算出未来该数据要进入的partition (partitionId会随着元数据一起写入)。

  2. 内存缓冲区溢出:当内存缓冲区满了,会写到文件(溢出写文件的时候,缓冲区禁止数据进入)。

  3. 文件过多就合并,文件过大就压缩。最终合并出来2个文件,一个file.out一个file.index(index就是索引,避免从头查询。记录的是partitionId在out文件中的起始位置和offset)

这是 Shuffle 的起点,发生在每个 Map 任务内部。

  1. 内存缓冲区 (In-Memory Buffer)

    • 目的 :Map 输出键值对不是直接写磁盘或网络,而是先写入一个 环形内存缓冲区 (mapreduce.task.io.sort.mb 参数控制大小,默认 100MB)。

    • 优点:避免频繁的小 I/O 操作,极大提升效率。

    • 内部结构:缓冲区本质是一个字节数组。写入时:

      • 序列化 <Key, Value> 成字节。
      • 同时写入数据的 元数据 (Metadata):包括 Key 的起始位置、Value 的起始位置、分区号 (Partition ID)、Value 的长度。元数据占固定大小(通常 16 bytes)。
      • 数据和元数据在缓冲区中分开存放。
  2. 分区 (Partitioning)

    • 写入缓冲区时,会立即调用 分区器 (Partitioner) 。默认是 HashPartitioner:partition = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks。
    • 元数据中就包含了这个计算出的 Partition ID。

    也就是这个键值对该去哪个分区,在map的shuffle阶段就已经确定好了。

  3. 溢出 (Spill)

    • 触发条件:

      • 当内存缓冲区使用量达到一个阈值 (mapreduce.map.sort.spill.percent,默认 80% = 80MB 满时)。
      • 或者 Map 任务结束时(清空最后的数据)。
    • 过程:

      • 锁定缓冲区:暂停新的写入。

      • 后台线程执行溢出:

        • 排序 (Sort) :对要溢出的 元数据区 进行排序。排序依据是:先按 Partition ID,然后在同一个 Partition 内按 Key。
        • 合并 (Combine - Optional) :如果用户配置了 Combiner,且溢出的次数 >= min.num.spills.for.combine (默认 3),则在排序后、写入磁盘前,对同一个 Partition 内、同一 Key 的 Value 执行 Combiner 进行本地聚合。
        • 写入磁盘 :根据排序后的元数据顺序,从缓冲区数据区读取对应的序列化字节块,写入到 Map 任务本地工作目录的一个 溢出文件 (Spill File) 中。一个溢出过程产生一个文件。
      • 解锁缓冲区:继续接收新的 Map 输出。

  4. 文件合并 (Merge)

    • 当 Map 任务结束前(所有数据处理完),或者达到合并条件时。

    • 过程:

      • 将之前产生的 所有 Spill File 和 内存缓冲区中剩余的数据(如果有)一起读取。
      • 使用 多路归并排序 (Multi-way Merge Sort) 算法,将这些文件合并成一个最终的、分区且分区内有序 的大文件 (file.out),通常还有一个对应的索引文件 (file.out.index)。
      • 索引文件的作用 :记录每个 Partition ID 在最终输出文件 file.out 中的 起始偏移量 (Start Offset) 和 结束偏移量 (End Offset) 。这极大加速了 Reduce 端拉取对应分区的数据。
    • 最终输出 :这个 file.out 和 file.index 就是 Map 任务最终的输出结果,存储在 Map 任务所在节点的本地磁盘上。


(二) Reduce 端 Shuffle
一句话总结
  1. 拉数据:向AM询问Mapper地址,然后多线程从mapper拉文件(rpc或者特定协议),拉到的文件放在Reduce的内存缓冲区
  2. 内存缓冲区溢出处理: 排序合并->写到临时文件
  3. 合并所有临时文件和缓冲区数据成为一个排序好的大文件(如果太多太多合并不过来,需要分轮次处理)

Reduce 任务启动后,主动从各个 Map 任务节点拉取属于自己的数据。

  1. 复制阶段 (Copy Phase / Fetch Phase)

    • 获取位置信息 :Reduce 任务启动后,会向 ApplicationMaster (AM) 询问所有已完成的 Map 任务的主机位置 (NodeManager 地址) 及其输出文件信息(包含文件位置和索引文件位置)。
    • 拉取线程 (Fetcher Threads) :Reduce 任务启动多个 HTTP/Fetcher 线程 (mapreduce.reduce.shuffle.parallelcopies 控制线程数,默认 5)。每个线程负责从一个或多个 Map 节点拉取数据。
    • 拉取目标 :每个 Fetcher 线程会去对应的 Map 节点,读取其最终输出文件 (file.out) 中 属于本 Reduce 任务负责的 Partition 的数据块。它利用索引文件 (file.index) 快速定位到该 Partition 在 file.out 文件中的精确偏移范围。
    • 传输方式:数据通过 HTTP 协议传输(早期版本)。更现代的框架可能使用更高效的 RPC 或专用协议。
    • 内存缓冲区 (Shuffle Buffer) :拉取到的数据并非直接写入 Reduce 的输入文件,而是先放入 Reduce 任务所在节点的一个 内存缓冲区 (mapreduce.reduce.shuffle.input.buffer.percent 控制占堆内存比例,默认 70%)。
  2. 内存管理与溢出 (Memory Management & Spill)

    • 触发条件 :当 Reduce 端内存缓冲区的数据量达到一定阈值 (mapreduce.reduce.shuffle.merge.percent,默认 66%)。

    • 后台线程执行溢出:

      • 将缓冲区中的数据 排序 (Sort) 并 合并 (Merge) 后,写入 Reduce 任务本地磁盘的一个临时文件中。
    • 目的:避免内存溢出 (OOM),并开始进行数据的初步聚合 / 排序。

  3. 合并阶段 (Merge Phase)

    • 输入来源:

      • 来自内存缓冲区的数据(当缓冲区没满,但所有 Map 任务都完成,或需要最后合并时)。
      • 之前内存溢写到磁盘的临时文件。
    • 过程 :同样使用 多路归并排序 (Multi-way Merge Sort) 算法。

    • 轮次 (Passes) :如果文件太多,可能进行多轮合并,每轮合并多个小文件成少量大文件。

    • 最终输出 :当所有 Map 输出的数据都拉取并合并完成后,会得到一个或多个 在 Reduce 本地磁盘上、按键完全排序 的文件。如果进行了多轮合并,最终通常是一个大文件。

    • 可选操作 :在合并过程中,如果配置了 Combiner,且合并轮次满足条件,也会执行 Combiner。

  4. 最终输入 (Reduce Input)

    • 合并完成后得到的最终排序好的文件,就是 Reduce 任务的输入。
    • Reduce 任务此时可以开始执行用户定义的 reduce() 函数。reduce() 函数接收一个 Key 和这个 Key 对应的 已排序好的所有 Value 的迭代器 (Iterable<Value>) 。这个分组和排序的能力,正是前面的 Shuffle 阶段辛苦工作的结果。

三、关键优化技术 & 问题解决

  1. 内存缓冲区:核心优化点,减少昂贵的磁盘 I/O 次数。设置不当会导致频繁溢出或 OOM。

  2. 排序算法:高效的内存排序 (QuickSort) 和磁盘归并排序 (MergeSort) 是关键。Hadoop 采用优化过的排序器。

  3. 索引文件:避免 Reduce 拉取整个 Map 输出文件,只拉取所需的分区数据,节省网络带宽和磁盘 I/O。

  4. Combiner :在 Map 端和 Reduce 端 Shuffle 过程中 本地聚合 相同 Key 的 Value,大幅减少需要传输和处理的数据量。解决数据膨胀问题。

  5. 并行拉取 (Fetcher Threads) :多个线程同时从不同 Map 节点拉取数据,加速复制阶段。解决网络传输瓶颈。

  6. 压缩 (Compression) :

    • Map 输出压缩 (mapreduce.map.output.compress) : 在 Map 端输出到磁盘或网络前压缩数据 (e.g., Snappy, LZ4, Gzip)。显著减少磁盘写入量和网络传输量。
    • Shuffle 拉取压缩:如果 Map 输出压缩了,Reduce 拉取后需要先解压。
  7. 自定义分区器 (Partitioner) :解决数据倾斜问题 (Data Skew),避免某些 Reduce 任务负载过重。例如,对 UserId 分区而不是默认的 Hash。

  8. 直接内存传输 (Shuffle Plugin / Optimized Fetch) :较新版本 (如 Hadoop 3.1+) 支持通过 RPC 或共享内存等更高效方式传输数据,减少 HTTP 开销和序列化 / 反序列化成本。

  9. 错误处理:

    • Map 任务失败:Reduce 会重新从其他成功的副本拉取数据。
    • Reduce 任务失败:需要重新启动该 Reduce 任务,重新拉取所有数据(因为数据在本地磁盘)。

总结

Shuffle 的本质是 大规模分布式排序分组传输。Hadoop MapReduce 的实现核心在于:

  1. 内存优先:用大缓冲区减少 I/O。

  2. 分区排序:在 Map 端按 Key 和 Partition 排序,为传输和 Reduce 输入做好准备。

  3. 索引定位:用索引文件精确定位目标数据块。

  4. 并行传输:多线程拉取加速。

  5. 多级归并:处理海量中间数据。

  6. 本地聚合 (Combiner) :大幅减少数据量。

理解这些细节对于 调优 MapReduce 作业性能 (如调整缓冲区大小、启用压缩、优化分区器、设计 Combiner) 和 排查 Shuffle 相关的问题 (如 OOM、数据倾斜、网络瓶颈) 至关重要。

相关推荐
卷毛迷你猪12 小时前
快速实验篇(B02)DWD 生产级升级
大数据·hive·hadoop
卷毛迷你猪13 小时前
快速实验篇(B03) 用户行为漏斗与转化率
大数据·hive·hadoop
卷毛迷你猪14 小时前
快速实验篇(B01)用户行为日志画像与 DWD 标准化
大数据·hive·hadoop
卷毛迷你猪17 小时前
快速实验篇(B04) 热门品类 Top10 与加权排名
大数据·hive·hadoop
Q26433650232 天前
【有源码】基于XGBoost的安卓应用市场数据分析与评分预测系统-面向应用市场的安卓App评分回归预测方法研究
大数据·hadoop·数据挖掘·数据分析·回归·spark·毕业设计
IT毕设梦工厂2 天前
计算机毕业设计选题推荐:基于大数据的培训机构信息分析与可视化|毕业设计选题|计算机毕设|选题推荐|毕设指导|项目定制|源码|高质量项目
大数据·hadoop·mysql·数据挖掘·数据分析·spark·课程设计
IT毕设梦工厂2 天前
计算机毕业设计选题推荐:基于大数据的杭州亚运会社交媒体互动数据可视化分析|毕业设计选题|计算机毕设|选题推荐|毕设指导|项目定制|源码|高质量项目
大数据·hadoop·信息可视化·数据挖掘·数据分析·课程设计·数据分析数据可视化
CarIise3 天前
Spring Boot整合MyBatis与文件上传:从分层架构到文件上传下载实战
数据仓库·hive·hadoop
计算机源码社3 天前
【27届大数据毕设】基于Spark的TikTok短视频属性分享与互动比率可视化分析系统 基于机器学习的TikTok短视频认证属性与互动效能关联分析
大数据·hadoop·python·机器学习·spark·毕业设计·课程设计
ha_lydms3 天前
MaxCompute中加密与解密函数
大数据·数据库·数据仓库·hadoop·dataworks·maxcompute·odps