3.1 MapReduce 编程模型


文档摘要

3.1 MapReduce 编程模型 第三章:Hadoop MapReduce 分布式计算框架 3.1 MapReduce 编程模型 MapReduce 并非一种全新的编程语言,而是一种编程模型或计算框架,旨在简化大规模数据集的并行处理。它将复杂、分布式的计算任务分解为两个核心阶段:Map(映射) 和 Reduce(归约)。这种简洁而强大的模型,使得开发者可以专注于业务逻辑的实现,而无需过多关注底层复杂的分布式细节,例如数据分片、任务调度、容错处理等,这些都由 Hadoop 框架自身来处理。 3.1.1 Map 阶段:数据映射与转换 MapReduce 流程的第一个阶段是 Map 阶段。

3.1 MapReduce 编程模型

第三章:Hadoop MapReduce 分布式计算框架

3.1 MapReduce 编程模型

MapReduce 并非一种全新的编程语言,而是一种编程模型计算框架,旨在简化大规模数据集的并行处理。它将复杂、分布式的计算任务分解为两个核心阶段:Map(映射)Reduce(归约)。这种简洁而强大的模型,使得开发者可以专注于业务逻辑的实现,而无需过多关注底层复杂的分布式细节,例如数据分片、任务调度、容错处理等,这些都由 Hadoop 框架自身来处理。

3.1.1 Map 阶段:数据映射与转换

MapReduce 流程的第一个阶段是 Map 阶段。此阶段的核心任务是对输入数据进行映射转换,将其转化为中间键值对 (key-value pair) 的形式。

输入与分片 (Input & Split)

MapReduce 作业的输入数据通常存储在 Hadoop 分布式文件系统 (HDFS) 中。框架会将输入数据划分为若干个独立的 InputSplit,每个 InputSplit 可以理解为数据的逻辑分片。InputSplit 的大小通常与 HDFS 的块大小 (Block Size) 相关,默认情况下,一个 InputSplit 对应一个 HDFS 块。

每个 InputSplit 将会被分配给一个 Mapper 任务进行处理。Mapper 任务是 Map 阶段的执行单元,负责读取分配给它的 InputSplit 中的数据,并根据预定义的 Map 函数进行处理。

Map 函数 (Map Function)

Map 函数是 Map 阶段的核心逻辑所在,由开发者根据具体的业务需求进行编写。其输入是 键值对 (key-value pair),通常由 InputFormat 组件从 InputSplit 中读取并解析得到。InputFormat 负责将原始输入数据(例如文本文件中的行)转换为适合 Map 函数处理的键值对形式。

Map 函数的输出也是 键值对 (key-value pair)。这些输出的键值对被称为 中间结果 (Intermediate Results),它们将被传递到后续的 Shuffle 和 Reduce 阶段进行进一步处理。

Map 阶段数据流 (Data Flow in Map Phase)

我们可以用 Mermaid 的 graph TD 图来清晰地展示 Map 阶段的数据流:

代码实践:Word Count 的 Map 函数

以经典的 Word Count 案例为例,假设我们的输入数据是文本文件,我们需要统计每个单词出现的次数。以下是 Java 代码实现的 Map 函数:

import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text word = new Text(); @Override public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); // 获取输入行 String[] words = line.split("\\s+"); // 按空格分割单词 for (String w : words) { word.set(w); // 设置单词为 Text 类型 context.write(word, one); // 输出 (word, 1) 键值对 } } }

代码详解:

  • WordCountMapper 类继承 Mapper 类: 指定了 Mapper 的输入键类型 (LongWritable),输入值类型 (Text),输出键类型 (Text),输出值类型 (IntWritable)。

    • LongWritable: 输入键类型,表示输入数据行的偏移量。

    • Text: 输入值类型,表示输入数据行(文本)。

    • Text: 输出键类型,表示单词。

    • IntWritable: 输出值类型,表示单词计数(固定为 1)。

  • map() 方法: Mapper 的核心方法,对每个输入的键值对进行处理。

    • key: 输入键,数据行的偏移量。

    • value: 输入值,数据行内容。

    • context: Mapper 的上下文对象,用于输出中间结果,以及获取作业配置信息等。

  • value.toString(): 将输入的 Text 类型值转换为 Java String 类型。

  • line.split("\\s+"): 使用正则表达式 "\\s+" (一个或多个空格) 分割输入行,得到单词数组。

  • word.set(w): 将遍历到的单词 w 设置为 Text 对象 word

  • context.write(word, one): 将单词 word 和计数 one (1) 以键值对的形式输出。context.write() 方法负责将中间结果写入到磁盘,供 Shuffle 和 Reduce 阶段使用。

总结 Map 阶段:

Map 阶段的主要职责是 数据转换。Mapper 任务接收 InputSplit 中的数据,通过执行 Map 函数,将原始数据转换为一系列的中间键值对。这些中间键值对代表了对输入数据的初步处理结果,为后续的聚合和分析奠定了基础。

3.1.2 Shuffle 和 Sort 阶段:数据混洗与排序

在 Map 阶段之后,MapReduce 框架会自动进入 Shuffle 和 Sort 阶段。这个阶段是 MapReduce 框架的核心和精髓所在,也是实现分布式计算的关键步骤。Shuffle 和 Sort 阶段的主要任务是将 Map 阶段输出的中间结果进行分区 (Partitioning)排序 (Sorting)分组 (Grouping),以便将相同键的数据发送到同一个 Reducer 任务进行处理。

分区 (Partitioning)

分区阶段的目标是决定将每个 Mapper 输出的中间键值对发送到哪个 Reducer 任务进行处理。默认情况下,MapReduce 使用 Hash Partitioner,它根据中间结果的键 (key) 的哈希值对 Reducer 的数量进行取模运算,从而将键值对均匀地分配到不同的 Reducer。开发者也可以自定义 Partitioner 来实现更复杂的分区策略,例如根据键的范围进行分区。

排序 (Sorting)

在每个分区内部,框架会对中间结果的键 (key) 进行排序。排序的目的是为了方便 Reduce 阶段进行数据聚合。默认情况下,排序是按照键的字典顺序进行的。开发者也可以自定义排序规则,例如根据键的数值大小或自定义的比较器进行排序。

分组 (Grouping)

分组阶段发生在排序之后,其目的是将具有相同键的中间结果分组在一起,以便将它们作为一组值传递给同一个 Reducer 任务进行处理。默认情况下,分组是根据键的相等性进行的。开发者也可以自定义分组规则,例如根据键的前缀或自定义的分组比较器进行分组。

数据混洗 (Shuffle)

Shuffle 是指将 Mapper 任务输出的中间结果通过网络传输到 Reducer 任务的过程。由于 Mapper 和 Reducer 任务通常运行在不同的节点上,因此 Shuffle 阶段涉及到大量的数据网络传输。为了提高 Shuffle 的效率,MapReduce 框架会进行一些优化,例如使用 Combiner 在 Map 端进行本地聚合,减少网络传输的数据量。

Shuffle 和 Sort 阶段数据流 (Data Flow in Shuffle and Sort Phase)

代码实践:Combiner (可选优化)

在 Word Count 案例中,我们可以在 Map 阶段之后,Shuffle 阶段之前,添加一个 Combiner 来进行本地的单词计数聚合,减少网络传输的数据量。以下是 Combiner 的 Java 代码实现:

import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class WordCountCombiner 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); // 输出 (word, sum) 键值对 } }

代码详解:

  • WordCountCombiner 类继承 Reducer 类: Combiner 的接口与 Reducer 相同,都是 Reducer 类。

  • reduce() 方法: Combiner 的核心方法,与 Reducer 的 reduce() 方法逻辑类似,接收相同键的一组值 (来自同一个 Mapper 输出的中间结果)。

  • sum += val.get(): 累加相同单词的计数。

  • context.write(key, result): 将单词 key 和聚合后的计数 result (sum) 以键值对的形式输出。

配置 Combiner:

要在 MapReduce 作业中使用 Combiner,需要在 Job 配置中进行设置:

job.setCombinerClass(WordCountCombiner.class);

总结 Shuffle 和 Sort 阶段:

Shuffle 和 Sort 阶段是 MapReduce 框架实现分布式计算的关键环节。通过分区、排序和分组操作,框架将 Map 阶段输出的中间结果按照键进行整理,确保相同键的数据被发送到同一个 Reducer 任务进行处理。Combiner 作为可选的优化组件,可以在 Map 端进行本地聚合,减少网络传输,提高作业效率。

3.1.3 Reduce 阶段:数据归约与聚合

MapReduce 流程的最后一个阶段是 Reduce 阶段。此阶段的核心任务是对 Shuffle 和 Sort 阶段处理后的数据进行归约聚合,最终生成最终结果。

输入与分组数据 (Input & Grouped Data)

Reducer 任务接收来自多个 Mapper 任务经过 Shuffle 和 Sort 阶段处理后的数据。输入到 Reducer 的数据是以 键 (key) 分组 的形式组织的。对于每个键,Reducer 接收到一个 迭代器 (Iterable),该迭代器包含了与该键相关联的所有值 (values)。这些值已经在 Shuffle 和 Sort 阶段被排序。

Reduce 函数 (Reduce Function)

Reduce 函数是 Reduce 阶段的核心逻辑所在,由开发者根据具体的业务需求进行编写。其输入是 键 (key) 和值迭代器 (Iterable)。Reduce 函数需要遍历值迭代器中的所有值,并根据业务逻辑进行聚合、计算或其他处理,最终输出 键值对 (key-value pair) 作为最终结果。

输出 (Output)

Reducer 函数的输出是最终的键值对结果。这些结果会被写入到指定的输出位置,例如 HDFS 上的文件。OutputFormat 组件负责将 Reducer 的输出结果转换为最终的输出格式,例如文本文件、SequenceFile 等。

Reduce 阶段数据流 (Data Flow in Reduce Phase)

代码实践:Word Count 的 Reduce 函数

在 Word Count 案例中,Reduce 函数的任务是将相同单词的计数进行累加,得到每个单词的总计数。以下是 Java 代码实现的 Reduce 函数:

import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class WordCountReducer 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); // 输出 (word, sum) 键值对 } }

代码详解:

  • WordCountReducer 类继承 Reducer 类: 指定了 Reducer 的输入键类型 (Text),输入值类型 (IntWritable),输出键类型 (Text),输出值类型 (IntWritable)。

  • reduce() 方法: Reducer 的核心方法,对每个输入的键和值迭代器进行处理。

    • key: 输入键,单词。

    • values: 输入值迭代器,包含相同单词的所有计数 (来自 Mapper 或 Combiner 的输出)。

    • context: Reducer 的上下文对象,用于输出最终结果,以及获取作业配置信息等。

  • sum += val.get(): 遍历值迭代器,累加相同单词的计数。

  • result.set(sum): 将累加后的总计数 sum 设置为 IntWritable 对象 result

  • context.write(key, result): 将单词 key 和总计数 result 以键值对的形式输出。context.write() 方法负责将最终结果写入到指定的输出位置。

总结 Reduce 阶段:

Reduce 阶段是 MapReduce 流程的最后一步,负责对 Shuffle 和 Sort 阶段整理后的数据进行最终的聚合和处理。Reducer 任务接收分组后的数据,通过执行 Reduce 函数,将相同键的值进行合并或计算,最终生成最终结果。

3.1.4 MapReduce 编程模型总结

MapReduce 编程模型的核心思想可以概括为 "分而治之,合而归一"

  • 分而治之 (Divide and Conquer): 将大规模数据集分解为小的 InputSplit,并行地由多个 Mapper 任务进行处理,实现数据的分布式处理。

  • 合而归一 (Combine and Aggregate): 通过 Shuffle 和 Sort 阶段将中间结果按照键进行整理,然后由 Reducer 任务对相同键的数据进行聚合和归约,最终得到全局的统计或分析结果。

MapReduce 编程模型的优势:

  • 简化分布式编程: 开发者只需关注 Map 和 Reduce 函数的逻辑实现,无需关注底层的分布式细节,例如数据分片、任务调度、容错处理等。

  • 高可扩展性: MapReduce 框架可以方便地扩展到大规模集群,处理 PB 甚至 EB 级别的数据。

  • 高容错性: 框架能够自动处理任务失败和节点故障,保证作业的可靠执行。

  • 适用于数据密集型计算: MapReduce 特别适合处理大规模数据集的批处理任务,例如数据清洗、日志分析、数据挖掘等。

MapReduce 编程模型的局限性:

  • 不擅长实时计算: MapReduce 是一种批处理框架,任务启动和数据Shuffle 阶段的开销较大,不适合低延迟的实时计算场景。

  • 不擅长迭代计算: 对于需要多次迭代的算法,例如机器学习中的迭代算法,MapReduce 的效率较低,因为每次迭代都需要重新启动 MapReduce 作业。

  • 编程模型相对固定: MapReduce 的编程模型相对固定,对于一些复杂的计算任务,可能需要进行复杂的 Map 和 Reduce 函数设计。

MapReduce 编程模型的核心组件:

  • InputFormat: 负责将输入数据转换为 Map 函数可以处理的键值对形式。

  • Mapper: 负责对输入数据进行映射和转换,输出中间键值对。

  • Partitioner: 负责将中间键值对分区,决定发送到哪个 Reducer 任务。

  • Sorter: 负责对每个分区内的中间键值对进行排序。

  • Combiner (可选): 在 Map 端进行本地聚合,减少网络传输数据量。

  • Reducer: 负责对 Shuffle 和 Sort 阶段处理后的数据进行归约和聚合,输出最终结果。

  • OutputFormat: 负责将 Reducer 的输出结果转换为最终的输出格式并写入到输出位置。

完整 Word Count 代码示例 (包含 Driver 类):

import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordCount { public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage: WordCount <input path> <output path>"); System.exit(-1); } Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(WordCountMapper.class); job.setCombinerClass(WordCountCombiner.class); // 可选 Combiner job.setReducerClass(WordCountReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } }

代码详解:

  • WordCount 类 (Driver 类): MapReduce 作业的驱动类,负责配置和启动 MapReduce 作业。

  • main() 方法: 程序的入口方法。

  • 参数校验: 检查命令行参数是否正确 (输入路径和输出路径)。

  • Configuration conf = new Configuration();: 创建 Hadoop 配置对象。

  • Job job = Job.getInstance(conf, "word count");: 创建 MapReduce 作业实例,并设置作业名称为 "word count"。

  • job.setJarByClass(WordCount.class);: 设置作业的 Jar 包类,框架会根据这个类找到包含 Mapper、Reducer 等类的 Jar 包。

  • job.setMapperClass(WordCountMapper.class);: 设置 Mapper 类。

  • job.setCombinerClass(WordCountCombiner.class);: 设置 Combiner 类 (可选)。

  • job.setReducerClass(WordCountReducer.class);: 设置 Reducer 类。

  • job.setOutputKeyClass(Text.class);: 设置输出键类型。

  • job.setOutputValueClass(IntWritable.class);: 设置输出值类型。

  • FileInputFormat.addInputPath(job, new Path(args[0]));: 设置输入路径,从命令行参数获取。

  • FileOutputFormat.setOutputPath(job, new Path(args[1]));: 设置输出路径,从命令行参数获取。

  • System.exit(job.waitForCompletion(true) ? 0 : 1);: 提交作业并等待完成。job.waitForCompletion(true) 方法会阻塞当前线程,直到作业完成。如果作业成功完成,返回 true,否则返回 falseSystem.exit() 方法根据作业的完成状态设置程序的退出码。

总结:

本章节详细介绍了 Hadoop MapReduce 分布式计算框架的编程模型,包括 Map 阶段、Shuffle 和 Sort 阶段、Reduce 阶段,以及 MapReduce 编程模型的优势和局限性。通过 Word Count 案例的代码实践和图文展示,希望读者能够深入理解 MapReduce 编程模型的原理和应用,为后续学习和使用 Hadoop MapReduce 框架打下坚实的基础。 掌握 MapReduce 编程模型是理解和应用 Hadoop 生态系统中其他大数据处理框架的关键一步,例如 Hive、Pig 等,它们都或多或少地受到了 MapReduce 编程思想的影响。


发布者: 作者: 转发
评论区 (0)
U