大厂 MapReduce 面试题集锦及参考答案
请详细介绍 MapReduce 的概念
MapReduce 是一种用于处理大规模数据集的编程模型和计算框架,最初由 Google 提出,旨在简化分布式计算任务的开发。它的核心思想是将复杂的数据处理任务分解为两个主要阶段:Map 和 Reduce,通过并行化处理实现高效的数据操作。
核心目标是让开发者在无需关心底层分布式系统细节(如任务调度、容错、数据分布)的情况下,专注于业务逻辑。例如,在统计文本中单词出现次数的场景中,Map 阶段将文本拆解为键值对(单词作为键,计数为值),Reduce 阶段则汇总相同键的值,得到最终结果。
关键特征包括:
- 横向扩展:通过增加计算节点提升处理能力,适用于 PB 级数据。
- 容错机制:自动处理节点故障,重新分配失败任务。
- 数据本地化:尽量在存储数据的节点上执行计算,减少网络传输开销。
MapReduce 通常与分布式文件系统(如 HDFS)配合使用,形成完整的大数据处理生态。例如,Hadoop 的实现使其成为早期大数据领域的基石技术。
阐述 MapReduce 的优点和缺点
优点:
- 高扩展性:通过添加廉价硬件节点即可扩展集群,适合处理海量数据。
- 简化编程:开发者只需实现 Map 和 Reduce 函数,无需管理分布式系统的复杂性。
- 自动容错:任务失败时自动重启,中间数据持久化存储保障可靠性。
- 批处理优化:适合离线数据处理场景,如日志分析、ETL 流程。
缺点:
- 高延迟:不适合实时或交互式查询,任务启动和中间数据交换耗时较长。
- 磁盘 I/O 瓶颈:Map 和 Reduce 阶段频繁读写磁盘,影响性能(后续框架如 Spark 改用内存计算优化此问题)。
- 灵活性不足:复杂的多阶段任务(如迭代计算)需要串联多个 MapReduce 作业,代码冗余度高。
- 资源利用率低:任务调度和启动开销大,短任务场景下效率较低。
描述 MapReduce 的架构组成及各部分的作用
MapReduce 架构包含以下核心组件:
Client | 提交作业到集群,指定输入路径、输出路径及 Map/Reduce 函数实现。 |
JobTracker | 资源管理和任务调度的主节点,分配任务到 TaskTracker,监控任务状态并处理故障。 |
TaskTracker | 工作节点上的守护进程,执行具体的 Map 或 Reduce 任务,定期向 JobTracker 汇报心跳。 |
HDFS | 存储输入数据和输出结果,确保数据分块分布在不同节点,支持数据本地化计算。 |
流程交互:
- Client 将作业配置和代码打包提交给 JobTracker。
- JobTracker 根据输入数据分片(Split)数量决定创建多少个 Map 任务,并分配到空闲的 TaskTracker。
- TaskTracker 启动子进程执行任务,Map 结果写入本地磁盘,Reduce 任务通过网络拉取数据并聚合。
说明 MapReduce 的工作原理,包括数据处理的整体流程
MapReduce 的数据处理流程分为五个阶段:
- 输入分片(Input Splitting)输入数据被分割为固定大小的块(如 128MB),每个分片由一个 Map 任务处理。分片策略影响负载均衡,需避免数据倾斜。
- Map 阶段每个 Map 任务读取分片数据,逐行处理并生成中间键值对。例如,统计词频时,Map 函数输出 <word, 1>。结果先写入内存缓冲区,溢出时排序并分区(按 Reduce 任务数量)后写入磁盘。
- Shuffle 和 Sort所有 Map 任务完成后,Reduce 任务从各个节点拉取属于自己分区的数据。数据在 Reduce 端按键排序,便于聚合。此阶段网络传输和磁盘 I/O 密集,常成为性能瓶颈。
- Reduce 阶段排序后的数据按键分组,Reduce 函数遍历每个键的值列表进行汇总(如累加计数)。结果最终写入 HDFS,每个 Reduce 任务生成一个输出文件。
- 输出结果存储在分布式文件系统中,格式可由用户自定义(如文本、序列文件)。
在 MapReduce 的各个阶段中,哪个阶段最耗费时间?请说明原因
Shuffle 和 Sort 阶段通常是耗时最长的环节,主要原因包括:
- 跨节点数据传输Map 任务的输出需通过网络传输到 Reduce 节点,数据量庞大时网络带宽成为瓶颈。例如,若 Map 生成 1TB 中间数据,需在集群内全量传输。
- 磁盘 I/O 压力Map 阶段将中间结果写入本地磁盘,Reduce 任务拉取数据时再次读写,高频的磁盘操作延长处理时间。
- 排序开销Reduce 节点需对所有接收到的数据按键排序,大规模数据下外部排序算法(多路归并)消耗大量 CPU 和内存资源。
优化手段:
- Combiner 函数:在 Map 端本地预聚合数据,减少传输量。
- 压缩中间数据:使用 Snappy 或 LZO 压缩算法降低网络和磁盘负载。
- 调整分区策略:避免数据倾斜,确保 Reduce 任务负载均衡。
尽管其他阶段(如 Map 执行)也可能因计算复杂度高而耗时,但 Shuffle 和 Sort 的资源竞争问题在多数场景下更为显著。
MapReduce 中的 Combine 组件的功能是什么?它能带来哪些好处?
Combine 是 MapReduce 中一个可选的本地聚合组件,通常运行在 Map 任务结束后、数据发送到 Reduce 节点之前。它的核心功能是在 Map 端对中间数据进行预聚合,以减少网络传输的数据量和 Reduce 阶段的负载。
功能细节:
- 本地聚合:对相同键(Key)的多个值(Value)进行合并。例如,在词频统计中,若某个 Map 任务多次输出
<"apple", 1>
,Combine 会将其合并为<"apple", 3>
。 - 数据压缩:通过减少键值对数量,降低后续 Shuffle 阶段的网络传输压力。
核心好处:
- 减少网络带宽消耗:中间数据在 Map 节点本地聚合后,跨节点传输的数据量显著下降。例如,若原始数据包含 1 亿条
<word, 1>
,Combine 可能将其压缩为 10 万条<word, N>
。 - 降低磁盘和 CPU 开销:Reduce 阶段需要处理的数据量减少,排序和聚合的计算成本随之降低。
- 提升整体性能:尤其在数据倾斜场景(如某些键出现频率极高)下,Combine 能避免单个 Reduce 任务成为瓶颈。
限制条件:Combine 的操作必须是幂等且可结合的。例如,求和、求最大值等操作适用,但求平均值则需谨慎(需记录总和与计数,在 Reduce 阶段最终计算)。
解释 MapReduce 中设置环形缓冲区的必要性
环形缓冲区(Circular Buffer)是 Map 任务中用于临时存储输出键值对的内存区域,其设计目标是平衡内存使用与磁盘 I/O 效率。
必要性分析:
- 减少磁盘写入频率:Map 任务逐行处理数据时,若每条结果直接写入磁盘,频繁的 I/O 操作会导致性能骤降。环形缓冲区通过批量写入(默认阈值 80% 满时触发)减少磁盘访问次数。
- 内存管理优化:环形结构允许覆盖旧数据,避免内存碎片。当缓冲区写满时,后台线程将数据排序并**溢写(Spill)**到磁盘,同时 Map 任务继续向剩余空间写入新数据。
- 排序预处理:溢写前,数据会按键排序并分区(Partition),为后续 Shuffle 阶段做好准备。
参数影响:
- 缓冲区大小(如默认 100MB)直接影响溢写频率。较大的缓冲区减少溢写次数,但占用更多内存。
- 溢写阈值(如 80%)需权衡内存利用率与任务稳定性——阈值过高可能导致写入阻塞。
说明 MapReduce 必须有 Shuffle 过程的原因
Shuffle 是 MapReduce 中连接 Map 和 Reduce 阶段的核心环节,其存在必要性源于分布式计算的底层逻辑:
- 数据分发需求:Map 任务的输出分散在多个节点,而 Reduce 任务需按键分组处理数据。例如,所有键为 "error" 的日志需发送到同一个 Reduce 任务。
- 数据排序与合并:Reduce 阶段要求输入数据按键有序,以便高效聚合。Shuffle 过程在传输数据的同时完成排序。
- 负载均衡:通过分区(Partitioning)将数据均匀分配到不同 Reduce 任务,避免单个任务过载。
不可替代性:
- 若跳过 Shuffle,Reduce 任务无法获取完整的关联数据集,导致计算结果错误。例如,统计全局单词频率时,若 "apple" 的键值对分散在多个未聚合的 Reduce 任务中,结果将不准确。
描述 MapReduce 的 Shuffle 过程,并阐述其优化方法
Shuffle 过程分为 Map 端和 Reduce 端两个阶段:
Map 端流程:
- 分区(Partitioning):根据键的哈希值将数据分配到不同 Reduce 任务对应的分区。
- 排序(Sorting):每个分区内的数据按键排序。
- 溢写(Spilling):排序后的数据写入磁盘,生成多个溢出文件。
- 合并(Merging):所有溢出文件归并为一个已分区且排序的大文件。
Reduce 端流程:
- 数据拉取(Fetch):从各个 Map 节点下载属于自己分区的数据。
- 归并排序(Merge Sort):将来自不同 Map 任务的数据合并为全局有序文件。
优化方法:
减少数据量 | 启用 Combine 预聚合、使用压缩算法(如 Snappy)压缩中间数据。 |
降低磁盘 I/O | 增大环形缓冲区大小、调整溢写阈值。 |
网络传输优化 | 启用 HTTP 压缩、调整并行拉取线程数。 |
避免数据倾斜 | 自定义分区策略,确保数据均匀分布(例如对热点键添加随机后缀再聚合)。 |
在 MapReduce 中,Reduce 如何获取 Map 的结果集?它是如何知道从哪里拉取数据的?
Reduce 任务通过**主动拉取(Pull)**机制获取 Map 结果,具体流程如下:
- 元数据管理:Map 任务完成后,会向 JobTracker(或 YARN 的 ResourceManager)注册输出位置,包括数据所在节点、分区信息等。这些元数据存储在中心化服务(如 Hadoop 的 JobTracker)中,供 Reduce 任务查询。
- 数据拉取流程:Reduce 任务启动后,向 JobTracker 请求其负责的分区对应的 Map 输出位置。根据元数据,Reduce 任务通过 HTTP 请求连接到各个 Map 节点,下载属于自己分区的数据文件。
- 容错机制:若某个 Map 节点故障,JobTracker 会重新调度该 Map 任务到其他节点,Reduce 任务从新节点拉取数据。数据拉取过程中,Reduce 任务会验证文件的校验和,确保数据完整性。
关键技术点:
- 分区映射表:JobTracker 维护了 Map 任务输出与 Reduce 任务的映射关系,例如分区 0 对应 Reduce 任务 1。
- 数据本地化:Reduce 会优先从同一机架的节点拉取数据,减少跨网络流量。
- 并行拉取:Reduce 任务同时启动多个线程从不同 Map 节点下载数据,提升吞吐量。
示例场景:假设集群有 100 个 Map 任务和 10 个 Reduce 任务,每个 Reduce 任务需从 100 个 Map 节点拉取属于自己的 10 个分区数据,最终合并处理。
请描述 Reduce 阶段的具体操作,包括是否进行分组以及分组的方式
Reduce 阶段是 MapReduce 流程中数据聚合的核心环节,负责将来自多个 Map 任务的中间结果合并为最终输出。这一阶段的操作不仅涉及数据的分组,还包含排序、合并和执行用户定义的 Reduce 函数。
操作流程分解:
- 数据拉取与归并Reduce 任务从各个 Map 节点拉取属于自己分区的数据(通过 Shuffle 过程),并将这些数据临时存储在本地磁盘。由于数据可能来自多个 Map 任务,Reduce 会通过多路归并排序将所有输入文件合并为一个全局有序的大文件。
- 分组(Grouping)分组是 Reduce 阶段的核心操作,目的是将相同键(Key)的所有值(Value)聚合在一起,形成 <Key, List<Value>> 的结构。例如,统计词频时,所有键为 "apple" 的值(如 1, 1, 1)会被合并为 <"apple", [1,1,1]>。分组方式:默认基于键的哈希值进行分组,但用户可通过自定义 Partitioner 或 Comparator 实现按范围、规则或业务逻辑的分组。
- 执行 Reduce 函数每组键值对会调用一次用户编写的 Reduce 函数,对值列表进行汇总计算(如累加、求平均或复杂业务逻辑)。结果最终写入分布式文件系统(如 HDFS),每个 Reduce 任务生成一个独立的输出文件。
分组的技术细节:
- 排序前置:由于数据在 Shuffle 阶段已按键排序,分组操作只需顺序扫描并合并连续相同键的数据,时间复杂度接近 O(n)。
- 内存优化:若值列表过大,Reduce 可能分批次加载数据到内存处理,避免内存溢出(OOM)。
MapReduce Shuffle 过程中使用的排序算法是什么
Shuffle 过程中使用的排序算法根据场景不同分为两种:
- Map 端的快速排序(Quick Sort)在 Map 任务将数据写入磁盘前,内存中的键值对会按键进行快速排序,以支持后续分区和溢写操作。选择原因:快速排序在内存中对随机数据具有较高的平均性能(时间复杂度 O(n log n))。
- Reduce 端的归并排序(Merge Sort)Reduce 任务从多个 Map 节点拉取数据后,需将多个有序文件合并为全局有序文件,此时采用多路归并排序。选择原因:归并排序适合外部排序(数据量大于内存容量),且稳定性高,不会打乱已部分有序的数据。
特殊场景处理:
- 若用户定义了自定义排序规则(如按数值降序),排序算法会依据该规则调整比较逻辑,但底层仍基于快速排序或归并排序实现。
解释在 MapReduce 的 Shuffle 过程中进行排序的目的
排序在 Shuffle 过程中扮演了关键角色,其目的主要包括:
- 支持 Reduce 阶段的聚合操作Reduce 任务需要将相同键的值集中处理,排序确保所有相同键的数据连续存储,只需一次线性扫描即可完成分组。例如,未排序的数据可能导致键 "apple" 分散在文件不同位置,迫使 Reduce 多次跳转读取。
- 提升数据压缩效率有序数据通常具有更高的局部性和重复性,便于使用压缩算法(如 Run-Length Encoding)减少存储和传输开销。
- 优化磁盘和网络性能排序后的数据在磁盘上以连续块存储,减少随机读取的寻道时间。网络传输中,有序数据可批量发送,减少协议头开销。
- 满足业务逻辑需求某些场景要求结果按特定顺序输出(如按时间戳排序的日志),Shuffle 阶段的排序为此类需求奠定了基础。
代价与权衡:
排序操作消耗大量 CPU 和内存资源,可能成为性能瓶颈。因此,在不需要排序的场景(如仅需哈希分组),用户可通过配置禁用排序以提升效率。
详细说明 map 阶段的数据是如何传递到 reduce 阶段的
数据从 Map 到 Reduce 的传递是一个跨节点、多步骤的流程,具体过程如下:
- Map 端处理分区(Partitioning):Map 任务根据 Reduce 任务数量(如 10 个)将输出数据分为多个分区,每个分区对应一个 Reduce 任务。默认使用哈希函数 hash(key) mod R 计算分区号。排序与溢写:每个分区的数据按键排序后写入本地磁盘,生成多个溢出文件(Spill File)。
- Shuffle 传输元数据上报:Map 任务完成后,向 JobTracker 报告输出文件的位置和分区信息。数据拉取:Reduce 任务启动后,通过 HTTP 请求从各个 Map 节点拉取属于自己分区的数据。
- Reduce 端归并拉取的数据在 Reduce 节点缓存到磁盘,合并为一个全局有序文件。归并过程中可能再次排序(若 Map 端排序规则与 Reduce 端不一致)。
关键优化技术:
- 数据压缩:在 Map 端使用 Snappy 等算法压缩数据,减少传输量。
- 并行拉取:Reduce 任务同时从多个 Map 节点下载数据,最大化利用网络带宽。
- 本地性优先:调度器优先将 Reduce 任务分配到含有部分数据的节点,减少跨机架传输。
介绍你所了解的 MapReduce 的 shuffle 机制有哪些
Shuffle 机制的实现因框架而异,以下为几种典型方案:
Hadoop MapReduce | 基于磁盘的 Shuffle,数据在 Map 端排序后写入磁盘,Reduce 通过多轮归并处理数据。 | 离线批处理,数据量极大的场景。 |
Spark Shuffle | 可选择基于磁盘或内存的 Shuffle,支持哈希(Hash)和排序(Sort)两种数据分配策略。 | 迭代计算、实时性要求较高的场景。 |
Tez Shuffle | 采用动态管道(Pipeline)传输,减少中间落盘次数,通过事件驱动模型提升效率。 | DAG 复杂任务,需多次数据交换的场景。 |
Flink Shuffle | 基于网络缓冲区的流水线传输,支持阻塞和非阻塞模式,允许在数据传输过程中并行处理。 | 流处理和批处理混合场景。 |
优化技术对比:
- Hadoop 的 Shuffle 稳定性高,但磁盘 I/O 开销大。
- Spark 的 Shuffle 可通过
Tungsten
优化内存管理,减少序列化开销。 - Flink 的 Shuffle 利用流水线技术,适合低延迟场景,但对网络稳定性要求较高。
演进趋势:
现代框架(如 Spark 和 Flink)逐渐采用随机 Shuffle(Hash-based) 替代全排序,以牺牲部分有序性换取更高的吞吐量,同时引入数据倾斜处理技术(如 Salting)应对分布不均问题。
请阐述 MapReduce 的数据处理过程,包括从输入数据到输出结果的完整流程
MapReduce 的数据处理流程是一个分阶段、高度并行化的过程,其核心目标是将大规模数据分解为可管理的任务单元,最终合并为全局结果。以下是关键步骤的详细展开:
1. 输入数据分片(Input Splitting)
输入数据(如 HDFS 中的文件)被划分为多个逻辑分片(Split),每个分片通常对应一个 HDFS 数据块(默认 128MB)。分片不实际切割数据,而是记录数据块的偏移量和长度,供 Map 任务读取。例如,一个 1GB 的文件会被分为 8 个分片,每个分片由一个 Map 任务处理。
2. Map 阶段
- 数据读取:Map 任务通过
InputFormat
类(如TextInputFormat
)读取分片数据,解析为键值对<K1, V1>
。例如,文本文件的一行可能被处理为<行号, 文本内容>
。 - 业务逻辑处理:用户编写的 Map 函数将
<K1, V1>
转换为中间键值对<K2, V2>
。例如,在词频统计中,输出<单词, 1>
。 - 本地聚合与分区:通过 Combiner 对相同键的值进行预聚合,再按分区规则(如哈希取模)将数据划分为多个分区,每个分区对应一个 Reduce 任务。
3. Shuffle 与 Sort 阶段
- Map 端排序:每个分区的数据按键排序后写入本地磁盘,生成多个溢出文件(Spill File)。
- 数据拉取:Reduce 任务从所有 Map 节点拉取属于自己分区的数据。
- 归并排序:Reduce 端将来自不同 Map 任务的数据合并为全局有序文件,确保相同键的数据连续存储。
4. Reduce 阶段
- 分组与聚合:Reduce 任务按键分组数据,执行用户定义的 Reduce 函数生成最终结果
<K3, V3>
。例如,将<"apple", [1,1,1]>
合并为<"apple", 3>
。 - 结果写入:输出通过
OutputFormat
类(如TextOutputFormat
)写入 HDFS,每个 Reduce 任务生成一个独立文件。
关键优化点:
- 数据本地化:调度器优先将 Map 任务分配到存储数据的节点,减少网络传输。
- 容错机制:若某个任务失败,JobTracker 会重新调度到其他节点执行。
解释 mapjoin 的实现原理,并举例说明其适用的应用场景
MapJoin(也称 Broadcast Join)是一种通过将小表加载到内存来加速关联操作的优化技术,适用于大表与小表关联的场景。
实现原理:
- 小表广播:在 Map 阶段,将小表的数据全量加载到每个 Map 任务的内存中,通常存储为哈希表(Hash Table)或字典结构。
- 关联处理:在处理大表的每条记录时,直接通过内存中的小表数据完成关联,无需 Shuffle 过程。例如,用户表(小)与订单表(大)通过用户 ID 关联时,Map 任务读取订单记录后,立即从内存中的用户表查找匹配信息。
- 结果输出:关联后的数据直接作为 Map 的输出,跳过了 Reduce 阶段。
适用场景:
- 小表与大表关联:小表数据量需足够小(通常不超过几百 MB),能完全放入内存。例如: 维度表与事实表关联(如商品信息表关联销售记录)。用户配置表关联日志数据。
- 低延迟查询:适用于 Hive 等场景下需要快速响应的交互式查询。
限制条件:
- 小表数据必须能完全放入内存,否则会导致 OOM(内存溢出)。
- 仅支持等值关联(Equi-Join),不支持非等值或复杂条件关联。
描述 reducejoin 的执行原理
ReduceJoin 是 MapReduce 中处理两个或多个大表关联的标准方法,其核心思想是通过 Shuffle 过程将关联键相同的数据汇集到同一 Reduce 任务中完成关联。
执行流程:
- Map 阶段标记数据来源:每个 Map 任务读取输入表的数据,并为每条记录添加来源标识。例如,表 A 的记录标记为 (A, 数据),表 B 的记录标记为 (B, 数据)。输出键为关联键(如订单 ID),值为数据与来源标识的组合。例如,<OrderID, (A, 产品信息)> 或 <OrderID, (B, 客户信息)>。
- Shuffle 阶段按关联键分组:所有相同 OrderID 的记录(无论来自哪个表)被分配到同一个 Reduce 任务。
- Reduce 阶段关联操作:Reduce 任务接收到的数据按来源标识分为两组(如表 A 和表 B)。通过嵌套循环遍历两组数据,生成笛卡尔积结果。例如,每个表 A 的记录与所有表 B 的记录匹配,输出关联后的完整记录。
缺点与优化:
- 数据倾斜风险:若某个关联键对应的数据量极大(如热门商品),会导致 Reduce 任务负载不均。
- 性能开销:Shuffle 阶段需传输全量数据,网络和磁盘开销较高。优化手段包括过滤无效数据或提前压缩关联键。
说明 MapReduce 不能产生过多小文件的原因
MapReduce 对小文件(如几 MB 甚至 KB 级别的文件)的处理存在显著性能瓶颈,主要原因包括:
- NameNode 内存压力:HDFS 中每个文件、目录或块的元数据(如位置、大小)约占用 150 字节内存。若存在数百万个小文件,NameNode 内存可能耗尽,导致集群不可用。
- Map 任务启动开销:每个小文件会生成一个独立的 Map 任务。例如,10,000 个 1MB 的文件会启动 10,000 个 Map 任务,而任务调度和初始化的时间可能远超过数据处理本身。
- 磁盘与网络效率低下:Map 任务读取小文件时,磁盘寻道时间占比高,无法充分利用顺序读取的高吞吐特性。Shuffle 阶段可能传输大量小文件,增加网络协议头(如 TCP/IP)的开销。
- 资源浪费:每个 Map 任务默认占用一个 Container(包含固定内存和 CPU),大量小任务导致资源碎片化,集群利用率下降。
解决方案:
- 文件合并:使用 HAR(Hadoop Archive)或 Hive 的
CONCATENATE
命令将小文件合并为大文件。 - 输出优化:在 Reduce 阶段控制输出文件数量,例如通过设置
hive.merge
参数自动合并结果。
介绍 MapReduce 中分区的概念和作用
分区(Partitioning)是 MapReduce 中控制数据如何分配到 Reduce 任务的核心机制,决定了哪些键值对会被发送到哪个 Reduce 任务进行处理。
核心概念:
- 分区数量:通常等于 Reduce 任务的数量,由用户通过
job.setNumReduceTasks()
设置。 - 分区规则:默认使用哈希函数(
hash(key) mod R
)计算分区号,但支持自定义分区逻辑(如按日期范围或业务规则)。
核心作用:
- 负载均衡:通过均匀分布数据到不同 Reduce 任务,避免单个任务过载。例如,若数据倾斜严重(如某个键占比 90%),自定义分区可将该键分散到多个分区。
- 数据分组:确保相同键的数据进入同一 Reduce 任务,满足聚合操作的需求。例如,统计每个用户的访问次数时,同一用户的所有记录必须发送到同一任务。
- 灵活控制输出:通过自定义分区,可将特定数据写入指定文件。例如,按国家分区后,每个国家的数据独立存储。
自定义分区示例:
public class CustomPartitioner extends Partitioner<Text, IntWritable> { @Override public int getPartition(Text key, IntWritable value, int numPartitions) { String country = key.toString().split("-")[0]; // 假设键格式为"国家-用户ID" return (country.hashCode() & Integer.MAX_VALUE) % numPartitions; } }
此代码将相同国家的数据分配到同一分区,适用于按国家汇总统计的场景。
优化注意事项:
- 避免分区数量过多(如设置 10,000 个 Reduce 任务),否则会产生大量小文件。
- 自定义分区需确保分布均匀,否则可能导致数据倾斜,反而降低性能。
分析 ReduceTask 数量和分区数量之间的关系
ReduceTask 数量与分区数量之间存在紧密的关联性,但两者并不总是严格相等。它们的核心关系可总结为:
- 默认情况下的强绑定:在 MapReduce 中,每个分区(Partition)对应一个 ReduceTask。例如,若设置 job.setNumReduceTasks(5),则分区数量默认也为 5,数据通过哈希函数分配到这 5 个分区。例外情况:若用户自定义了 Partitioner 且逻辑导致分区号超过 ReduceTask 数量,框架会取模运算,可能导致数据分布不均甚至错误。
- 分区数决定 ReduceTask 的数据范围:每个 ReduceTask 必须处理至少一个分区,但一个分区只能由一个 ReduceTask 处理。例如,若分区数为 10 而 ReduceTask 数量设为 3,则部分 ReduceTask 会处理多个分区(如第 1 个 ReduceTask 处理分区 0-3,第 2 个处理 4-6,第 3 个处理 7-9)。若分区数小于 ReduceTask 数量,多余的 ReduceTask 会空跑,生成空文件。
- 数据倾斜的影响:若分区策略不合理(如某个键占比过高),即使 ReduceTask 数量足够,仍会导致负载不均。例如,日志数据中 90% 的请求来自同一个用户 IP,对应的 ReduceTask 将处理大部分数据。
最佳实践:
- 保持一致:通常将 ReduceTask 数量与分区数量设为相同值,确保一一对应。
- 动态调整:根据数据特征动态计算 ReduceTask 数量,例如通过采样预估键的分布,再设置合理的分区数。
MapReduce 中 Map 的分片大小是如何确定的
Map 分片(InputSplit)的大小由数据存储特性和用户配置参数共同决定,目标是平衡任务负载与数据处理效率。
核心影响因素:
- HDFS 块大小:默认分片大小等于 HDFS 块大小(如 128MB)。例如,若文件大小为 300MB,HDFS 将其分为 3 个块(128MB+128MB+44MB),则生成 3 个分片。
- 配置参数:mapreduce.input.fileinputformat.split.minsize:分片的最小值(默认 1)。mapreduce.input.fileinputformat.split.maxsize:分片的最大值(默认 Long.MAX_VALUE)。实际分片大小计算公式为: 例如,若设置 maxSize=64MB,则 128MB 的块会被拆分为两个 64MB 的分片。
- 文件格式与压缩:不可分割文件:如 GZIP 压缩文件,无法并行处理,整个文件作为一个分片。可分割文件:如 BZIP2 或 LZO(带索引),允许按块划分分片。
特殊场景处理:
- 小文件合并:若输入包含大量小文件,可通过
CombineFileInputFormat
合并多个小文件为一个分片,减少 Map 任务数量。
请描述 MapReduce 进行两表 join 操作的具体流程
两表 Join 的典型实现是 ReduceJoin,其流程分为三个阶段:
- Map 阶段标记数据来源:每个 Map 任务读取表 A 或表 B 的数据,输出键为 Join Key(如订单 ID),值为数据记录并附加来源标识。例如: 表 A 记录:<OrderID, (A, ProductInfo)>表 B 记录:<OrderID, (B, CustomerInfo)>
- Shuffle 阶段按 Join Key 分组:所有相同 OrderID 的记录被分配到同一个 Reduce 任务。数据在 Reduce 端按键排序,确保相同键的记录连续存储。
- Reduce 阶段执行关联操作:Reduce 任务接收到的数据按来源标识分为两组:表 A 和表 B。遍历两组数据生成笛卡尔积。例如:若某组数据为空,可选择内连接(过滤)或外连接(补空值)。
优化方法:
- Bloom Filter 过滤:在 Map 阶段过滤不可能关联的记录,减少 Shuffle 数据量。
- Secondary Sort:对关联键以外的字段排序,减少 Reduce 阶段的计算量。
请手写一段简单的 MapReduce 程序,实现对输入数据的某种处理功能(可自行设定处理逻辑)
以下是一个统计文本中单词首字母频率的示例:
Mapper 类:
public class InitialMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private Text initial = new Text(); private final static IntWritable ONE = new IntWritable(1); @Override public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String[] words = line.split(" "); for (String word : words) { if (!word.isEmpty()) { initial.set(word.substring(0, 1).toUpperCase()); // 取首字母并大写 context.write(initial, ONE); } } } }
Reducer 类:
public class SumReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); @Override public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } }
驱动类配置:
public class InitialDriver extends Configured implements Tool { public static void main(String[] args) throws Exception { int res = ToolRunner.run(new Configuration(), new InitialDriver(), args); System.exit(res); } @Override public int run(String[] args) throws Exception { Job job = Job.getInstance(getConf(), "Initial Count"); job.setJarByClass(InitialDriver.class); job.setMapperClass(InitialMapper.class); job.setReducerClass(SumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); return job.waitForCompletion(true) ? 0 : 1; } }
逻辑说明:
- Mapper 提取每个单词的首字母作为键,输出
<"A", 1>
等形式。 - Reducer 对相同首字母的计数求和,得到各首字母的出现频率。
在 MapReduce 任务执行过程中,reduce 任务在什么时机开始执行
Reduce 任务的启动时机由框架调度策略决定,具体分为两个阶段:
- Shuffle 阶段提前拉取(Overlap Phase):部分 Map 完成后:当部分 Map 任务完成(通常完成 5% 后),Reduce 任务开始从已完成的 Map 节点拉取数据。这种机制称为 Shuffle 的并行启动,旨在减少总体作业时间。边拉取边处理:Reduce 任务在拉取数据的同时进行归并排序,但真正的 Reduce 函数执行需等待所有 Map 任务完成。
- 全部 Map 完成后的最终执行:严格依赖:为确保数据完整性,Reduce 任务必须等待所有 Map 任务完成后,才能确认已获取全部数据,进而执行最终的排序和聚合。容错场景:若某个 Map 任务失败并重启,Reduce 会重新拉取该 Map 的新输出数据。
关键点:
- Reduce 任务的启动时间点与执行阶段不同:启动可能在 Map 未完成时,但核心计算逻辑(如调用用户 Reducer)必须等待 Map 全部结束。
- 推测执行(Speculative Execution):若某个 Map 任务过慢,框架会启动备份任务,此时 Reduce 可能同时从原始和备份任务拉取数据,以先到达的为准。
MapReduce 的 reduce 阶段使用的是什么排序方式?
Reduce 阶段使用的排序方式是多路归并排序(Multi-way Merge Sort),这是为处理大规模数据而设计的外部排序算法。其核心目标是将来自多个 Map 任务的中间结果合并为全局有序的数据流。
排序过程细节:
- 数据拉取与临时存储: Reduce 任务从各个 Map 节点拉取数据后,将数据缓存在本地磁盘的临时文件中。由于数据可能来自多个 Map 任务且每个 Map 的输出已经局部有序,Reduce 需要将这些部分有序的文件合并为全局有序。
- 归并策略: 采用多路归并算法,每次从多个输入文件中读取数据块,选择当前最小的键进行输出。归并过程中会动态调整内存缓冲区大小,以平衡内存使用与磁盘 I/O 频率。
- 排序规则: 默认按键的字典序排序,但用户可通过自定义 Comparator 实现按数值、日期等规则排序。
选择归并排序的原因:
- 适合外部排序:数据量远大于内存容量时,归并排序通过分批读取和写入磁盘,避免内存溢出。
- 稳定性:归并排序是稳定排序,不会打乱数据原有的部分有序性(如 Map 端已排序的数据块)。
性能优化:
- 内存缓冲区调整:增大
mapreduce.task.io.sort.mb
参数可减少归并轮数,提升效率。 - 并行归并:使用多线程同时处理多个临时文件,充分利用多核 CPU。
说明 MapReduce 确定 MapTask 数量的方法和依据
MapTask 数量由输入数据的物理存储结构和用户配置参数共同决定,核心逻辑如下:
- 基础依据:输入分片(InputSplit)数量每个 InputSplit 对应一个 MapTask。例如,一个 1GB 的未压缩文本文件在 HDFS 上分为 8 个 128MB 的块,默认生成 8 个 MapTask。InputSplit 的划分通过 InputFormat 实现,例如 TextInputFormat 按行切分,CombineFileInputFormat 合并小文件。
- 关键配置参数:mapreduce.input.fileinputformat.split.minsize:分片的最小尺寸阈值。若设为 256MB,则 128MB 的 HDFS 块不会被单独处理,而是与其他块合并。mapreduce.input.fileinputformat.split.maxsize:分片的最大尺寸阈值。若设为 64MB,则 128MB 的块会被拆分为两个 64MB 的分片。
- 特殊文件格式处理:不可分割文件:如 GZIP 压缩文件,无论多大均作为一个 InputSplit,对应一个 MapTask。可分割文件:如 LZO(带索引)或 Parquet 格式,允许按块划分分片,并行处理。
示例计算:
假设一个 500MB 的文本文件存储在 HDFS(块大小 128MB),配置 split.minsize=64MB
和 split.maxsize=256MB
,则分片数为:
- 文件被分为 4 个块(128MB x3 + 116MB)。
- 根据
maxsize=256MB
,前三个 128MB 块各为一个分片,最后一个 116MB 块单独处理,总 MapTask 数量为 4。
影响 MapReduce 中 Map 数量的因素有哪些?
Map 数量主要由以下因素决定:
数据存储特性 | HDFS 块大小、文件数量、压缩格式(是否支持分割)直接影响 InputSplit 的划分。 |
用户配置参数 |
和
显式控制分片尺寸,间接决定 MapTask 数量。 |
输入格式实现 | 自定义
可覆盖默认分片逻辑,例如按数据库分片或按时间范围划分数据。 |
小文件合并策略 | 使用
将多个小文件合并为一个分片,显著减少 MapTask 数量。 |
特殊场景分析:
- 大量小文件:若存在 10,000 个 1MB 文件,默认生成 10,000 个 MapTask,导致调度开销激增。此时需启用小文件合并。
- 压缩文件:GZIP 文件不可分割,1GB 文件仅生成 1 个 MapTask;而 BZIP2 文件可分割,生成 8 个 MapTask(按 128MB 块划分)。
对于 MapReduce 的 map 进程和 reducer 进程,如何选择合适的 JVM 垃圾回收器以提高吞吐量?
JVM 垃圾回收器的选择需根据任务内存使用特征和GC 停顿容忍度权衡。MapReduce 任务通常追求高吞吐量,核心策略如下:
垃圾回收器对比:
Parallel GC | 默认选择,适合多核 CPU 和堆内存较大(如 8GB+)的任务,以吞吐量为优先。 |
|
G1 GC | 适合堆内存更大(如 16GB+)且要求低延迟的任务,通过分区回收减少 Full GC 停顿。 |
|
CMS GC | 已逐渐被 G1 取代,适用于对停顿敏感但内存中等的任务(如 4-8GB)。 |
|
优化建议:
- 监控 GC 日志:通过
-XX:+PrintGCDetails
分析 Full GC 频率和耗时。若 Full GC 频繁,需增大堆内存或调整回收器。 - 堆内存分配: 设置 mapreduce.map.memory.mb 和 mapreduce.reduce.memory.mb 为物理内存的 70%-80%,避免频繁 GC。新生代与老年代比例调整(如 -XX:NewRatio=2 表示新生代占堆的 1/3)。
- 避免内存泄漏:确保用户代码中未驻留大对象引用,尤其在 Reducer 的全局变量中。
场景示例:
- Map 任务:处理数据为短生命周期对象,选择 Parallel GC 并增大新生代大小(
-XX:NewSize=512m
)。 - Reduce 任务:需缓存大量数据做聚合,选择 G1 GC 并调大堆内存(
-Xmx8g
)。
如何合理划分 MapReduce 的 task 数目?有哪些需要考虑的因素?
Task 数目(包括 MapTask 和 ReduceTask)的划分需平衡资源利用率和任务执行效率,关键因素包括:
核心考虑因素:
- 数据规模与分布: MapTask 数量应接近输入数据的分片数,避免过多小任务或过少大任务。ReduceTask 数量需根据数据键的分布调整,例如数据倾斜时增加 ReduceTask 以分散负载。
- 集群资源容量: 总 Task 数不应超过集群的 Container 容量(由 YARN 的 yarn.nodemanager.resource.memory-mb 和 CPU 核数决定)。例如,若集群有 100 个 Container,同时运行 80 MapTask + 20 ReduceTask 较合理。
- 任务类型: CPU 密集型任务(如复杂计算)应减少并行度,避免频繁上下文切换。I/O 密集型任务(如数据清洗)可增加并行度,充分利用磁盘和网络带宽。
实践建议:
- MapTask 数量: 默认由 InputSplit 数量决定。对小文件问题,强制合并分片(如设置 split.maxsize=256MB)。
- ReduceTask 数量: 初始值可设为集群可用 Container 数的 0.95~1.75 倍(参考 Hadoop 默认规则)。通过数据采样预估键的基数,按 ReduceTask数 ≈ 总键数 / 每个Reduce处理键数 调整。
高级技巧:
- 动态调整:在作业运行时根据已完成 Task 的速度预测剩余时间,动态申请或释放 Container。
- 数据倾斜处理:对热点键添加随机前缀(Salting),强制分散到多个 ReduceTask,最后再合并结果。
在 MapReduce 作业执行过程中,中间数据存储在什么位置?是否会存储在内存中?
MapReduce 的中间数据(即 Map 任务的输出)优先存储在内存中,但最终会溢写到本地磁盘,具体流程如下:
- 内存缓冲区(Memory Buffer)Map 任务将输出的键值对先写入环形内存缓冲区,默认大小为 100MB(由 mapreduce.task.io.sort.mb 参数控制)。写入条件:当缓冲区填充达到阈值(默认 80%)时,触发**溢出(Spill)**操作,将数据排序后写入磁盘。
- 本地磁盘临时文件溢出文件(Spill File)存储在 Map 任务所在节点的本地文件系统(非 HDFS)中,路径由 mapreduce.cluster.local.dir 指定。多轮溢出:若 Map 任务处理的数据量极大,可能生成多个溢出文件,最终合并为一个有序大文件。
- 内存与磁盘的协同机制性能优先:内存缓冲区减少磁盘 I/O 次数,但受限于内存容量,溢写不可避免。数据持久化:中间数据不存储到 HDFS 的原因是避免网络传输开销,且 HDFS 设计目标为持久化存储,而非临时数据。
关键结论:
- 内存仅用于临时缓存,最终中间数据落地到本地磁盘。
- 容错处理:若 Map 任务失败,重新运行的实例会重新生成中间数据,无需从 HDFS 恢复。
在 Mapper 端进行 combiner 操作后,除了能提高处理速度,从 Mapper 端到 Reducer 端的数据量会发生怎样的变化?
Combiner 的核心作用是减少 Mapper 到 Reducer 的数据传输量,本质是在 Map 端提前执行一次 Reduce 逻辑的本地聚合。
数据量变化示例:
假设某 Mapper 输出以下键值对:
<"apple", 1>, <"apple", 1>, <"banana", 1>, <"apple", 1>
- 无 Combiner:传输 4 条记录到 Reducer。
- 有 Combiner:在 Map 端合并为
<"apple", 3>, <"banana", 1>
,仅传输 2 条记录。
数据压缩效果:
- 理想情况:若键的重复度高(如词频统计),Combiner 可减少 50%~90% 的 Shuffle 数据量。
- 极端场景:若键完全唯一(如 UUID),Combiner 无效果,数据量不变。
限制条件:
- 操作可结合性:Combiner 的逻辑必须满足结合律。例如,求和、取最大值可合并,但求平均值需特殊处理(如传递累加值和计数)。
- 执行次数不固定:Combiner 可能执行 0 次、1 次或多次,取决于溢出文件的数量。
当 map 输出的数据超出其小文件内存限制时,数据会落地到磁盘还是 HDFS 中?请说明原因。
当 Map 任务的输出数据超过内存缓冲区容量时,数据会落地到本地磁盘,而非 HDFS。原因包括:
- 性能优化:本地磁盘的写入速度远高于网络传输到 HDFS 的速度,避免 Shuffle 阶段成为性能瓶颈。HDFS 的写入需经过网络传输、多副本复制等步骤,不适合高频临时数据存储。
- 数据生命周期:中间数据是临时性的,Reduce 任务完成后即可删除,无需 HDFS 的持久化保障。HDFS 设计用于长期存储,而 MapReduce 中间数据仅需保留到作业结束。
- 资源隔离:本地磁盘读写由单个节点处理,避免多节点协作的开销。若使用 HDFS 存储中间数据,可能因其他作业的 HDFS 操作导致资源争用。
溢写流程细节:
- 数据在本地磁盘以多个有序溢出文件形式存储,最终合并为一个文件供 Reduce 任务拉取。
- 文件内容按键排序,并包含分区信息(如分区 0 到分区 N)。
MapReduce 中从 Map 到 Reduce 的默认分区机制是怎样的?
默认分区机制是 HashPartitioner,其核心逻辑为:
partition = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
执行步骤:
- 计算哈希值:对键(Key)调用
hashCode()
方法,取绝对值以避免负数。 - 取模运算:将哈希值对 Reduce 任务数取模,确保每个键映射到确定的分区。
特点与影响:
- 均匀分布:假设键的哈希值分布均匀,数据将平均分配到各 Reduce 任务。
- 数据倾斜风险:若某些键的哈希值冲突(如热点数据),会导致分区负载不均。
示例场景:
- Reduce 任务数设为 3,键 "user123" 的哈希值为 123456,则分区号为
123456 % 3 = 0
。 - 所有相同键的数据必然进入同一分区,确保 Reducer 能正确聚合。
适用性与局限性:
- 简单有效:适用于无特殊分布要求的场景。
- 自定义需求:若需按业务规则分区(如按日期范围),需实现
Partitioner
接口。
以 wordcount 为例,详细描述 MapReduce 的各个流程,包括 map 阶段和 reduce 阶段分别是如何处理数据的。
1. 输入分片(Input Splitting)
- 输入文本文件被划分为多个逻辑分片(例如 128MB/片),每个分片由一个 MapTask 处理。
2. Map 阶段处理逻辑
- 数据读取:MapTask 逐行读取分片内容,生成
<行偏移量, 行文本>
键值对。 - 分词处理:
输出
<"apple", 1>, <"banana", 1>, <"apple", 1>
等中间结果。 - Combiner 优化(可选):在 Map 端合并相同单词的计数,例如将两个
<"apple", 1>
合并为<"apple", 2>
。
3. Shuffle 与 Sort 阶段
- 分区与排序:MapTask 按 HashPartitioner 将数据分配到对应分区,并按单词字典序排序。
- 数据拉取:ReduceTask 从所有 MapTask 拉取属于自己分区的数据,合并后得到全局有序的
<单词, [1,1,...]>
列表。
4. Reduce 阶段处理逻辑
- 计数求和:
输出最终结果如
<"apple", 3>, <"banana", 1>
。
5. 结果输出
- 每个 ReduceTask 生成一个结果文件(如
part-r-00000
),存储在 HDFS 中。
流程关键点:
- 数据本地化:MapTask 优先在存储输入数据的节点执行。
- 容错机制:若某个 Task 失败,框架自动重新调度到其他节点执行。
在 MapReduce 作业执行过程中,中间数据存储在什么位置?是否会存储在内存中?
MapReduce 的中间数据(即 Map 任务的输出)优先存储在内存中,但最终会溢写到本地磁盘,具体流程如下:
- 内存缓冲区(Memory Buffer)Map 任务将输出的键值对先写入环形内存缓冲区,默认大小为 100MB(由 mapreduce.task.io.sort.mb 参数控制)。写入条件:当缓冲区填充达到阈值(默认 80%)时,触发**溢出(Spill)**操作,将数据排序后写入磁盘。
- 本地磁盘临时文件溢出文件(Spill File)存储在 Map 任务所在节点的本地文件系统(非 HDFS)中,路径由 mapreduce.cluster.local.dir 指定。多轮溢出:若 Map 任务处理的数据量极大,可能生成多个溢出文件,最终合并为一个有序大文件。
- 内存与磁盘的协同机制性能优先:内存缓冲区减少磁盘 I/O 次数,但受限于内存容量,溢写不可避免。数据持久化:中间数据不存储到 HDFS 的原因是避免网络传输开销,且 HDFS 设计目标为持久化存储,而非临时数据。
关键结论:
- 内存仅用于临时缓存,最终中间数据落地到本地磁盘。
- 容错处理:若 Map 任务失败,重新运行的实例会重新生成中间数据,无需从 HDFS 恢复。
在 Mapper 端进行 combiner 操作后,除了能提高处理速度,从 Mapper 端到 Reducer 端的数据量会发生怎样的变化?
Combiner 的核心作用是减少 Mapper 到 Reducer 的数据传输量,本质是在 Map 端提前执行一次 Reduce 逻辑的本地聚合。
数据量变化示例:
假设某 Mapper 输出以下键值对:
<"apple", 1>, <"apple", 1>, <"banana", 1>, <"apple", 1>
- 无 Combiner:传输 4 条记录到 Reducer。
- 有 Combiner:在 Map 端合并为
<"apple", 3>, <"banana", 1>
,仅传输 2 条记录。
数据压缩效果:
- 理想情况:若键的重复度高(如词频统计),Combiner 可减少 50%~90% 的 Shuffle 数据量。
- 极端场景:若键完全唯一(如 UUID),Combiner 无效果,数据量不变。
限制条件:
- 操作可结合性:Combiner 的逻辑必须满足结合律。例如,求和、取最大值可合并,但求平均值需特殊处理(如传递累加值和计数)。
- 执行次数不固定:Combiner 可能执行 0 次、1 次或多次,取决于溢出文件的数量。
当 map 输出的数据超出其小文件内存限制时,数据会落地到磁盘还是 HDFS 中?请说明原因。
当 Map 任务的输出数据超过内存缓冲区容量时,数据会落地到本地磁盘,而非 HDFS。原因包括:
- 性能优化:本地磁盘的写入速度远高于网络传输到 HDFS 的速度,避免 Shuffle 阶段成为性能瓶颈。HDFS 的写入需经过网络传输、多副本复制等步骤,不适合高频临时数据存储。
- 数据生命周期:中间数据是临时性的,Reduce 任务完成后即可删除,无需 HDFS 的持久化保障。HDFS 设计用于长期存储,而 MapReduce 中间数据仅需保留到作业结束。
- 资源隔离:本地磁盘读写由单个节点处理,避免多节点协作的开销。若使用 HDFS 存储中间数据,可能因其他作业的 HDFS 操作导致资源争用。
溢写流程细节:
- 数据在本地磁盘以多个有序溢出文件形式存储,最终合并为一个文件供 Reduce 任务拉取。
- 文件内容按键排序,并包含分区信息(如分区 0 到分区 N)。
MapReduce 中从 Map 到 Reduce 的默认分区机制是怎样的?
默认分区机制是 HashPartitioner,其核心逻辑为:
partition = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
执行步骤:
- 计算哈希值:对键(Key)调用
hashCode()
方法,取绝对值以避免负数。 - 取模运算:将哈希值对 Reduce 任务数取模,确保每个键映射到确定的分区。
特点与影响:
- 均匀分布:假设键的哈希值分布均匀,数据将平均分配到各 Reduce 任务。
- 数据倾斜风险:若某些键的哈希值冲突(如热点数据),会导致分区负载不均。
示例场景:
- Reduce 任务数设为 3,键 "user123" 的哈希值为 123456,则分区号为
123456 % 3 = 0
。 - 所有相同键的数据必然进入同一分区,确保 Reducer 能正确聚合。
适用性与局限性:
- 简单有效:适用于无特殊分布要求的场景。
- 自定义需求:若需按业务规则分区(如按日期范围),需实现
Partitioner
接口。
以 wordcount 为例,详细描述 MapReduce 的各个流程,包括 map 阶段和 reduce 阶段分别是如何处理数据的。
1. 输入分片(Input Splitting)
- 输入文本文件被划分为多个逻辑分片(例如 128MB/片),每个分片由一个 MapTask 处理。
2. Map 阶段处理逻辑
- 数据读取:MapTask 逐行读取分片内容,生成
<行偏移量, 行文本>
键值对。 - 分词处理:
输出
<"apple", 1>, <"banana", 1>, <"apple", 1>
等中间结果。 - Combiner 优化(可选):在 Map 端合并相同单词的计数,例如将两个
<"apple", 1>
合并为<"apple", 2>
。
3. Shuffle 与 Sort 阶段
- 分区与排序:MapTask 按 HashPartitioner 将数据分配到对应分区,并按单词字典序排序。
- 数据拉取:ReduceTask 从所有 MapTask 拉取属于自己分区的数据,合并后得到全局有序的
<单词, [1,1,...]>
列表。
4. Reduce 阶段处理逻辑
- 计数求和:
输出最终结果如
<"apple", 3>, <"banana", 1>
。
5. 结果输出
- 每个 ReduceTask 生成一个结果文件(如
part-r-00000
),存储在 HDFS 中。
流程关键点:
- 数据本地化:MapTask 优先在存储输入数据的节点执行。
- 容错机制:若某个 Task 失败,框架自动重新调度到其他节点执行。
分析 MapReduce 数据倾斜产生的原因,并提出相应的解决方案
数据倾斜是 MapReduce 作业中常见的性能瓶颈,本质是某些键(Key)的数据量远高于其他键,导致部分任务负载过重。以下是原因与解决方案的深度分析:
产生原因
- 键分布不均:自然数据特征:例如,电商订单数据中某些商品 ID 的访问量极高,或日志中特定用户 IP 的请求占比过大。业务逻辑设计:如使用时间戳作为键,导致数据按时间段集中。
- 分区策略缺陷:默认的 HashPartitioner 可能无法均匀分配热点键。例如,若热点键的哈希值集中在某几个分区,对应的 ReduceTask 将处理更多数据。
- Reduce 任务数不足:ReduceTask 数量过少时,即使键分布均匀,也可能因单个任务处理的数据量过大而出现瓶颈。
解决方案
数据预处理 | 数据可提前分析且倾斜键可识别 | 对输入数据采样,识别热点键后拆分为多个子键(如
,
),分散到不同分区。 |
自定义分区器 | 需要精准控制数据分布 | 实现
接口,根据业务逻辑将热点键分配到多个分区。例如,将订单 ID 按前缀分片。 |
Combiner 优化 | 中间数据可局部聚合 | 在 Map 端提前聚合重复键,减少传输到 Reduce 的数据量。需确保 Combiner 逻辑符合结合律。 |
动态调整 Reduce 数量 | 数据规模动态变化且无法预知键分布 | 根据作业运行时统计的键频率动态增加 ReduceTask 数量。 |
Map Join 替代 Reduce Join | 关联表中存在小表(可加载到内存) | 将小表广播到所有 Map 任务,避免 Shuffle 阶段的数据倾斜(详见问题 37)。 |
案例说明:
- 场景:日志分析中 80% 的请求来自 5% 的用户 IP。
- 处理:在 Map 阶段为这些 IP 添加随机后缀(如
ip_123_1
,ip_123_2
),使数据分散到多个 ReduceTask,最终再合并统计结果。
解释 Map Join 能够解决数据倾斜问题的原理
Map Join 的核心原理是将小表全量加载到内存,避免 Shuffle 阶段的网络传输和数据倾斜,适用于大表关联小表的场景。
执行流程:
- 小表加载:在作业启动前,将小表数据从 HDFS 读取到内存(如通过 DistributedCache 或广播变量)。内存中构建哈希表,键为关联字段,值为整行数据。
- Map 阶段关联:处理大表数据时,直接通过内存中的哈希表查找关联键,生成合并结果。例如,订单表(大表)关联用户表(小表)时,Map 任务读取订单记录后,立即从内存中获取用户信息,输出 <order_id, (order_info, user_info)>。
解决数据倾斜的关键点:
- 消除 Shuffle 阶段:所有关联操作在 Map 端完成,避免热点键在 Reduce 端集中处理。
- 并行化处理:每个 Map 任务独立处理关联逻辑,负载均匀分布。
限制条件:
- 小表必须能放入内存:通常要求小表大小不超过 Map 任务堆内存的 70%(考虑哈希表结构开销)。
- 仅支持等值连接:Map Join 无法处理非等值关联(如范围查询)。
参数配置:
- 在 Hive 中可通过
set hive.auto.convert.join=true;
启用自动 Map Join,并通过hive.mapjoin.smalltable.filesize
设置小表阈值。
在 MapReduce 运行过程中,如果发生 OOM(内存溢出),通常会在哪些位置出现?请分析原因
OOM 错误可能出现在以下三个阶段,原因各有不同:
1. Map 阶段
- 原因: 内存缓冲区溢出:mapreduce.task.io.sort.mb 设置过大,超过 JVM 堆内存。用户代码内存泄漏:Map 函数中误用全局集合(如静态 List)累积数据。
- 典型报错:
java.lang.OutOfMemoryError: Java heap space
。
2. Shuffle 阶段
- 原因: Map 端溢出文件过多:大量小文件导致归并排序时内存不足。Reduce 端拉取数据过快:未及时写入磁盘,缓冲区溢出。
- 典型报错:
java.lang.OutOfMemoryError: Unable to create new native thread
(归并线程过多)。
3. Reduce 阶段
- 原因: 聚合数据量过大:单个 ReduceTask 处理的数据超过 JVM 堆内存,尤其是未使用 Combiner 时。用户代码低效:如使用嵌套循环处理数据,导致对象无法释放。
解决方案:
- 调整内存参数: 增大 mapreduce.map.memory.mb 和 mapreduce.reduce.memory.mb,并同步调整 JVM 堆大小(-Xmx)。
- 优化数据结构: 使用更紧凑的数据类型(如 IntWritable 替代 LongWritable 存储小整数)。
- 启用 Combiner:减少 Shuffle 数据量。
MapReduce 在数据处理过程中进行了几次排序?分别是在哪些阶段和场景下进行的?
MapReduce 在以下三个阶段进行排序,排序是框架高效执行的关键机制:
1. Map 端溢写排序
- 触发条件:当 Map 内存缓冲区达到溢出阈值时,数据按键排序后写入磁盘。
- 排序规则:默认按键的字典序,可通过
RawComparator
自定义。 - 输出文件:生成多个内部有序的临时文件。
2. Shuffle 阶段归并排序
- 触发条件:ReduceTask 拉取多个 Map 输出文件后,执行多路归并排序。
- 排序规则:与 Map 端排序一致,确保全局有序。
- 优化手段:调整
mapreduce.task.io.sort.factor
(一次归并的文件数)以平衡内存与 I/O。
3. Reduce 端最终排序
- 触发条件:Reduce 处理前对已归并的数据再次排序,确保相同键连续。
- 排序必要性:即使数据已全局有序,仍可能因归并策略导致局部无序。
特殊场景:
- Secondary Sort:通过自定义
GroupingComparator
实现值排序,例如按时间戳对同一用户的日志排序。
介绍 MapReduce 支持的压缩方式及其特点
MapReduce 支持多种压缩格式,选择取决于压缩比、速度和是否支持分割:
Gzip | 高 | 慢 | 否 | 归档存储、冷数据备份 |
Bzip2 | 极高 | 极慢 | 是 | 需高压缩比的离线分析 |
Snappy | 低 | 极快 | 否 | 中间数据、实时处理 |
LZO | 中 | 快 | 是(需索引) | 需平衡速度与分割能力的场景 |
Zstandard | 高 | 快 | 否 | 替代 Gzip 的高性能压缩 |
配置方法:
- 启用中间数据压缩:
- 输出结果压缩:
选型建议:
- 中间数据:优先选择速度快的 Snappy 或 LZO,减少 Shuffle 时间。
- 最终输出:若存储成本敏感,选择 Gzip 或 Zstandard;若需支持后续并行读取,选择 Bzip2 或带索引的 LZO。
在 MapReduce 中,如何处理一个大文件?有哪些需要注意的要点?
处理大文件时,MapReduce 的核心思路是将文件切分为多个逻辑分片(InputSplit),每个分片由一个 MapTask 处理。以下是关键步骤和注意事项:
处理流程:
- 分片策略:可分割文件:如未压缩的文本文件,按 HDFS 块大小(默认 128MB)生成分片。例如,1GB 文件会被分为 8 个分片,启动 8 个 MapTask。不可分割文件:如 GZIP 压缩文件,无论多大均作为一个分片,仅启动 1 个 MapTask。
- 分片调整:通过参数 mapreduce.input.fileinputformat.split.minsize 和 split.maxsize 控制分片尺寸。例如,设置 maxsize=256MB 会将 128MB 的块合并为更大的分片。
注意事项:
- 数据本地化:优先将 MapTask 调度到存储该分片的节点,避免跨节点数据传输。
- 小文件合并:若大文件由多个小文件组成(如 1000 个 1MB 文件),需使用
CombineFileInputFormat
合并分片,减少 MapTask 数量。 - 压缩格式选择:处理大文件时优先选用支持分割的压缩格式(如 BZIP2、LZO),否则单个 MapTask 可能成为性能瓶颈。
性能优化:
- 并行处理:确保分片数量与集群可用资源匹配,避免 MapTask 过多导致调度开销,或过少导致资源闲置。
- 内存管理:大文件处理可能占用较多内存,需调整
mapreduce.map.memory.mb
参数,防止 OOM 错误。
MapReduce 与普通程序相比,有哪些本质上的区别?
MapReduce 与普通程序的核心区别在于分布式计算范式和容错机制,具体体现在以下方面:
执行环境 | 分布式集群,多节点并行处理 | 单机或少量节点,串行/多线程处理 |
数据处理模式 | 分片处理,移动计算到数据(Data Locality) | 数据集中存储,计算逻辑在固定位置执行 |
容错能力 | 自动重启失败任务,支持任务级别容错 | 依赖外部机制(如事务回滚),容错成本高 |
编程抽象 | 开发者仅需实现 map 和 reduce 函数 | 需自行管理线程、进程、网络通信等底层细节 |
关键差异点:
- 扩展性:MapReduce 可线性扩展至数千节点,普通程序受限于单机资源。
- 数据本地化:MapReduce 优先在存储数据的节点执行计算,减少网络传输;普通程序通常需要主动拉取数据。
- 任务隔离性:MapReduce 的每个 Task 运行在独立 JVM 中,资源隔离性强;普通程序多线程共享内存,易相互干扰。
典型场景:
- MapReduce 适用:TB 级日志分析、分布式排序、ETL 处理。
- 普通程序适用:实时计算、低延迟事务处理(如数据库操作)。
请简单描述 MapReduce 的运行启动过程及其主要流程
MapReduce 作业的启动过程涉及资源申请、任务调度、数据处理等多个阶段:
- 作业提交:客户端将作业配置和 JAR 包上传至 HDFS,向 YARN ResourceManager 提交作业。ResourceManager 分配一个 ApplicationMaster(AM)容器,负责协调任务执行。
- 资源协商:AM 向 ResourceManager 申请 Container 资源(内存、CPU)用于运行 MapTask 和 ReduceTask。资源分配策略受队列配置、优先级和集群负载影响。
- 任务执行:Map 阶段:各 MapTask 读取 InputSplit,执行 map 函数,输出中间结果到本地磁盘。Shuffle 阶段:ReduceTask 从所有 MapTask 拉取数据,归并排序后输入到 reduce 函数。Reduce 阶段:聚合处理数据,结果写入 HDFS。
- 完成与清理:AM 监控任务状态,失败任务自动重试,最终结果写入指定目录后释放资源。
关键机制:
- 心跳检测:AM 定期与 NodeManager 通信,确保任务存活。
- 推测执行:对慢任务启动备份任务,防止个别节点拖慢整体进度。
深入解释 MapReduce 的 Shuffle 原理,包括数据在该过程中的流转和处理方式
Shuffle 是 MapReduce 中连接 Map 和 Reduce 的核心阶段,负责将 Map 输出数据分发到对应 ReduceTask,流程如下:
Map 端处理:
- 内存缓冲区:Map 输出数据先写入环形缓冲区(默认 100MB),达到阈值(80%)后溢写到磁盘。
- 分区与排序:溢写前按
Partitioner
计算分区号(如HashPartitioner
),并对分区内数据按键排序。 - Combiner 优化:若启用 Combiner,在溢写前对同一分区内的数据局部聚合,减少数据量。
- 磁盘合并:多次溢写生成的文件最终归并为一个有序大文件,按分区存储。
Reduce 端处理:
- 数据拉取:ReduceTask 通过 HTTP 从各 MapTask 节点拉取属于自己分区的数据。
- 归并排序:拉取的数据在内存和磁盘中多路归并,生成全局有序的输入流。
- 分组处理:相同键的数据被分到同一组,调用 reduce 函数前执行二次排序(如 Secondary Sort)。
性能瓶颈与优化:
- 网络带宽:Shuffle 可能占用大量网络资源,可通过压缩中间数据(如 Snappy)缓解。
- 磁盘 I/O:调整
mapreduce.task.io.sort.factor
(一次归并的文件数)减少磁盘操作次数。
MapReduce 中 Map 的个数与哪些因素相关?
MapTask 数量由输入数据特征和配置参数共同决定,主要影响因素包括:
- 输入文件大小与分片规则:HDFS 块大小(默认 128MB)直接影响分片数量。例如,1GB 文件生成 8 个分片,对应 8 个 MapTask。可分割性:文本文件、Parquet 等格式支持分片;GZIP 等压缩格式不可分割,整个文件作为一个分片。
- 用户参数配置:mapreduce.input.fileinputformat.split.minsize:分片的最小尺寸。若设为 256MB,则 128MB 的块会合并到相邻分片。mapreduce.input.fileinputformat.split.maxsize:分片的最大尺寸。若设为 64MB,128MB 的块会被拆分为两个分片。
- 输入格式实现:自定义 InputFormat 可覆盖默认分片逻辑。例如,按数据库分页查询划分分片,或按时间范围切分日志。
示例场景:
- 场景 1:10 个未压缩的 200MB 文本文件,HDFS 块大小 128MB。 每个文件分为 2 个分片(200MB / 128MB ≈ 1.56,向上取整),总 MapTask 数为 10 x 2 = 20。
- 场景 2:1 个 1GB 的 GZIP 文件,不可分割,仅生成 1 个 MapTask。
调优建议:
- 避免过多小文件:使用
CombineFileInputFormat
合并小文件,减少 MapTask 数量。 - 平衡负载:确保分片大小均匀,防止部分 MapTask 处理数据量过大。
Reduce 的个数与小文件个数之间存在怎样的关系?
Reduce 任务的数量直接影响输出文件的个数,因为每个 ReduceTask 会生成一个独立的输出文件。这种机制导致以下现象:
- 小文件问题:当 Reduce 任务数设置过高且数据量较小时,每个 ReduceTask 处理的数据量可能极少,甚至为空,导致生成大量小文件。例如,设置 1000 个 ReduceTask 处理 1GB 数据,每个 Reduce 仅处理约 1MB,最终生成 1000 个小文件。
- 文件合并需求:HDFS 对小文件存储效率低(元数据开销大),因此需通过
hive.merge
参数或后续合并作业(如ALTER TABLE ... CONCATENATE
)优化。
关键因素:
- 数据分布均匀性:若数据倾斜严重,部分 ReduceTask 可能处理大量数据,而其他任务输出空文件。
- 业务逻辑需求:全局排序(如
ORDER BY
)通常需设置reduce.tasks=1
,强制生成单个文件。
调优建议:
- 动态调整 Reduce 数量:根据数据量估算合理值,公式为
reduce.tasks = min(数据总量 / 每个 Reduce 处理量, 集群最大并行度)
。 - 文件格式优化:使用支持合并的格式(如 ORC、Parquet),减少小文件对存储的影响。
对比说明 MR 的 Shuffle 和 Hive 的 Shuffle(即 MR)在实现和应用上的异同点
Hive 的 Shuffle 本质上基于 MapReduce,但在应用层封装了优化逻辑,以下是详细对比:
控制粒度 | 开发者需手动配置参数(如分区器、排序规则) | 通过 HiveQL 自动生成配置,简化用户操作 |
小文件处理 | 需自行合并输入/输出文件 | 内置小文件合并策略(如
) |
执行引擎扩展 | 仅支持 MapReduce | 可切换至 Tez 或 Spark,优化 Shuffle 效率 |
数据压缩 | 需显式启用中间数据压缩 | 自动根据配置选择压缩格式(如
) |
相同点:
- 数据分区、排序、网络传输等核心流程完全一致。
- 依赖相同的容错机制(任务重试、推测执行)。
应用场景差异:
- MapReduce:适合定制化需求(如复杂分片逻辑)。
- Hive:适合通过 SQL 快速实现 ETL,屏蔽底层细节。
比较 MR 和 Spark 的区别,并阐述 MR 具有哪些优势?
计算模型 | 基于磁盘的批处理,每阶段数据落盘 | 基于内存的 DAG 计算,中间结果缓存内存 |
API 灵活性 | 仅提供 map 和 reduce 接口 | 支持 RDD、DataFrame、SQL、流处理等多种抽象 |
延迟 | 高(分钟级) | 低(亚秒级流处理) |
容错机制 | 通过任务重试实现 | 基于 RDD 血缘(Lineage)重建数据 |
MapReduce 的核心优势:
- 超大规模数据稳定性:MapReduce 的阶段性落盘机制更适合 PB 级数据容错,避免内存不足导致作业失败。
- 成熟度与兼容性:作为 Hadoop 生态基石,与 HDFS、YARN 深度集成,适合传统企业级环境。
- 资源隔离性:每个 Task 在独立 JVM 中运行,资源争用风险低。
适用场景:
- MapReduce:离线日志分析、海量数据归档处理。
- Spark:迭代计算(如机器学习)、实时流处理。
在 MapReduce 中,排序方式有哪些?使用 Order By 时会启动几个 Reduce 进行处理?
MapReduce 的排序机制贯穿整个流程:
- Map 端排序: 数据在溢写磁盘前按键字典序排序,排序规则可通过 RawComparator 自定义。
- Shuffle 归并排序: ReduceTask 拉取数据后执行多路归并排序,确保全局有序。
- Reduce 端分组排序: 通过 GroupingComparator 实现值排序(如 Secondary Sort)。
使用 ORDER BY
时的 Reduce 数量:
- 默认行为:Hive 的
ORDER BY
会强制启动 1 个 ReduceTask,以实现全局有序。 - 优化方案:若需并行排序,可结合
DISTRIBUTE BY
和SORT BY
,按分区键分配数据到多个 ReduceTask,每个分区内有序。例如: 此时 Reduce 数量由key_partition
的基数决定。
是否有用 mapreduce 完成过 hive - sql 计算?如果有,请简述其具体流程
Hive 本质上通过将 SQL 转换为 MapReduce 作业执行计算,流程如下:
- HiveQL 解析: 解析 SQL 语句,生成抽象语法树(AST),优化逻辑计划(如谓词下推、列剪枝)。
- 逻辑计划转 MapReduce: JOIN 操作:根据表大小选择 Map Join 或 Reduce Join。GROUP BY:通过 Map 端 Combiner 预聚合,Reduce 端最终汇总。ORDER BY:强制单 Reduce 任务或结合分区键分发数据。
- 作业提交与执行: 生成 MapReduce 作业配置(如 InputFormat、OutputFormat),提交到 YARN 集群。每个 Hive 操作(如 SELECT、WHERE)映射为 Map 或 Reduce 阶段的任务链。
- 结果写回: 输出数据写入 HDFS,格式由 STORED AS 指定(如 TEXTFILE、PARQUET)。
示例:WordCount 的 Hive 实现
CREATE TABLE docs (line STRING); LOAD DATA INPATH '/input' INTO TABLE docs; SELECT word, COUNT(1) FROM docs LATERAL VIEW EXPLODE(SPLIT(line, ' ')) tmp AS word GROUP BY word;
- Map 阶段:
EXPLODE
和SPLIT
由 MapTask 执行,生成<word, 1>
。 - Reduce 阶段:按
word
分组求和,结果写入 HDFS。
优化实践:
- 参数调优:通过
set hive.optimize.reducededuplication=true;
减少重复任务。 - 数据压缩:启用中间数据压缩(如 Snappy)降低 Shuffle 开销。
在两表进行 join 操作时,mapreduce 是如何处理数据的?请详细描述处理过程
MapReduce 处理两表 JOIN 的核心思路是将关联键相同的数据分发到同一 ReduceTask,具体实现分为以下两种模式:
1. Reduce 端 JOIN(Common Join)
- 适用场景:两表均为大表,或无法将小表加载到内存。
- 处理流程: Map 阶段: 为每条记录打标签标识来源表(例如 tag=0 表示表 A,tag=1 表示表 B)。输出键值对 <关联键, (tag, 记录)>。例如,订单表记录输出为 <user_id, (0, order_info)>,用户表记录输出为 <user_id, (1, user_info)>。Shuffle 阶段: 按关联键分区排序,确保相同 user_id 的数据进入同一 ReduceTask。Reduce 阶段: 遍历同一键的所有值,将表 A 和表 B 的记录分别存入临时列表。执行笛卡尔积合并数据,输出 <user_id, (order_info, user_info)>。
2. Map 端 JOIN(Map Join)
- 适用场景:其中一张表足够小(可放入内存)。
- 处理流程: 预处理:将小表通过 DistributedCache 分发到所有 MapTask 节点。Map 阶段: 在内存中构建小表的哈希表(键为关联字段)。读取大表数据,直接通过哈希表查找关联键,合并后输出结果。
性能对比:
Reduce 端 JOIN | 高 | 低 | 大表关联大表 |
Map 端 JOIN | 无 | 高 | 大表关联小表 |
优化技巧:
- 倾斜键处理:对热点键添加随机前缀(如
user123_1
、user123_2
),分散到多个 ReduceTask 处理后再合并。 - BloomFilter 过滤:在 Map 阶段用布隆过滤器跳过不匹配的键,减少 Shuffle 数据量。
请描述一个 MapReduce 统计程序的大致过程,包括输入数据、处理逻辑和输出结果等方面
以经典的 WordCount 程序为例,其处理流程如下:
输入数据:
- 文本文件(如
input.txt
),内容为多行英文句子。
Map 阶段:
- 分片读取:每个 MapTask 处理一个 InputSplit(例如 128MB 的文件块)。
- 逐行处理: 输入格式为 <行偏移量, 行内容>,例如 <0, "hello world">。
- 分词与发射: 将行内容拆分为单词,输出 <单词, 1> 键值对。
Shuffle 阶段:
- Combiner 预聚合(可选):在 Map 端合并相同单词的计数,例如将两个
<"apple", 1>
合并为<"apple", 2>
。 - 分区与排序: 按单词哈希值分区,确保相同单词进入同一 ReduceTask。分区内数据按键字典序排序,生成 <"apple", [2, 1]> 的中间结果。
Reduce 阶段:
- 分组求和: 对每个单词的计数列表累加,得到最终词频。
- 结果输出: 每个 ReduceTask 生成一个文件(如 part-r-00000),格式为 <单词, 出现次数>。
性能瓶颈与调优:
- 内存缓冲区:调整
mapreduce.task.io.sort.mb
减少溢写次数。 - Reduce 数量:根据单词基数合理设置 ReduceTask 数量,避免小文件或数据倾斜。
请解释 mr 和 spark 的 shuffle 机制,包括它们的实现原理、数据流转过程以及两者之间的差异
MapReduce Shuffle:
- 实现原理: Map 端:数据写入内存缓冲区,溢写时按分区排序后生成磁盘文件。Reduce 端:通过 HTTP 拉取对应分区的数据,多路归并排序后输入 Reduce 函数。
- 数据流转: 磁盘密集型:每个阶段的中间数据必须落盘,适合处理超大数据但延迟较高。严格排序:Map 输出和 Reduce 输入均按键有序,适合需要全局排序的场景。
Spark Shuffle:
- 实现原理: Hash Shuffle:每个 MapTask 为每个 ReduceTask 生成一个文件,适用于小规模数据。Sort Shuffle(默认):MapTask 输出合并为单个排序文件,并生成索引文件供 ReduceTask 读取。
- 数据流转: 内存优先:数据优先缓存内存,内存不足时溢写到磁盘。弹性数据集:通过 RDD 血缘关系实现容错,无需重复计算。
核心差异:
数据存储 | 全程依赖磁盘,稳定性高 | 内存 + 磁盘,性能更高但需警惕 OOM |
排序机制 | 强制全局排序 | 仅 Sort Shuffle 排序,Hash Shuffle 无序 |
容错方式 | 重算整个 Shuffle 阶段 | 通过 RDD 血缘局部重算 |
网络优化 | 原生实现,无特殊优化 | 使用 Netty 框架,支持零拷贝和流式传输 |
性能影响:
- MapReduce:适合单次大批量处理,稳定性强但延迟高。
- Spark:适合迭代计算和低延迟场景,但需精细调整内存参数。
配置示例:
- MapReduce:通过
mapreduce.reduce.shuffle.parallelcopies
控制并发拉取数。 - Spark:设置
spark.shuffle.file.buffer
调整内存缓冲区大小。
17年+码农经历了很多次面试,多次作为面试官面试别人,多次大数据面试和面试别人,深知哪些面试题是会被经常问到。 在多家企业从0到1开发过离线数仓实时数仓等多个大型项目,详细介绍项目架构等企业内部秘不外传的资料,介绍踩过的坑和开发干货,分享多个拿来即用的大数据ETL工具,让小白用户快速入门并精通,指导如何入职后快速上手。 计划更新内容100篇以上,包括一些企业内部秘不外宣的干货,欢迎订阅!