MapReduce vs Spark

151 阅读4分钟

MapReduce 与 Spark 的关系

基本关系

MapReduce 和 Spark 是两代大数据处理框架:

  • MapReduce:Google 2004年提出的分布式计算模型,Hadoop实现了开源版本
  • Spark:UC Berkeley 2010年开发的分布式计算引擎,可视为MapReduce的进化版

Spark 继承了 MapReduce 的核心思想(数据分区、移动计算而非数据),但通过内存计算和更丰富的API显著提升了性能和易用性。

核心区别

特性MapReduceSpark
数据存储磁盘为主内存为主
处理速度较慢比MapReduce快10-100倍
编程模型仅Map和Reduce丰富的API(80+操作)
中间结果写入磁盘保留在内存
迭代计算多次读写磁盘,效率低数据可缓存在内存,高效
实时处理不支持支持(Spark Streaming)

实际例子:单词计数

详细执行流程

假设有以下输入文本:

Hello world
Hello MapReduce
1. Map 阶段(打标签)

Map 阶段不进行汇总,只是转换数据格式并标记:

("Hello", 1)
("world", 1)
("Hello", 1)
("MapReduce", 1)
  • Map 只是将每个单词转换为(单词, 1)的键值对
  • 它没有统计"Hello"出现了几次,只是给每次出现打上标记
2. Shuffle 阶段(重组)

Shuffle 将相同key的数据发送到同一个Reducer:

("Hello", [1, 1]) → 发送到Reducer 1
("world", [1]) → 发送到Reducer 2
("MapReduce", [1]) → 发送到Reducer 3
  • 相同单词的所有标记被发送到同一个Reducer
  • 这确保了每个Reducer能看到所有与特定单词相关的数据
3. Reduce 阶段(统计)

Reduce 阶段对收到的数据进行汇总计算:

("Hello", 2)
("world", 1)
("MapReduce", 1)
  • 现在才真正进行计数
  • 每个Reducer只处理分配给它的单词

代码实现对比

MapReduce 实现
// 1. Map阶段
public class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    
    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
        StringTokenizer itr = new StringTokenizer(value.toString());
        while (itr.hasMoreTokens()) {
            word.set(itr.nextToken());
            context.write(word, one);  // 输出 (单词, 1)
        }
    }
}

// 2. Reduce阶段
public class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable result = new IntWritable();
    
    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);  // 输出 (单词, 总数)
    }
}
Spark 实现
val textFile = spark.read.textFile("hdfs://...")
val counts = textFile.flatMap(line => line.split(" "))  // 分割每行为单词
                     .map(word => (word, 1))           // 转换为 (单词, 1) 的键值对
                     .reduceByKey(_ + _)               // 按键聚合,相加值
counts.saveAsTextFile("hdfs://...")

执行过程对比

MapReduce 执行流程:
  1. Map:将文本分割为单词,输出 (单词, 1) 的键值对
  2. 写入磁盘:Map输出结果写入本地磁盘
  3. Shuffle:将相同单词的记录发送到同一个Reducer
  4. 读取磁盘:Reducer从磁盘读取数据
  5. Reduce:统计每个单词的出现次数
  6. 写入结果:将结果写入HDFS
Spark 执行流程:
  1. 创建RDD:从文件创建初始RDD
  2. 转换操作
    • flatMap 分割文本为单词
    • map 转换为 (单词, 1) 的键值对
    • reduceByKey 聚合相同单词的计数
  3. 执行计划:Spark创建DAG(有向无环图)并优化执行计划
  4. 执行:数据尽可能保留在内存中处理
  5. 输出结果:触发行动操作,将结果写入存储系统

为什么Spark更高效?

  1. 内存计算:中间结果保留在内存中,减少I/O开销
  2. 惰性求值:转换操作不立即执行,等到行动操作时才执行,可优化整体计划
  3. DAG引擎:构建完整的执行计划,优化任务调度和数据流
  4. 更丰富的API:不限于Map和Reduce,提供更多高级操作

使用场景

MapReduce 适合:

  • 简单的批处理作业
  • 数据量大但计算相对简单的场景
  • 内存资源有限的环境

Spark 适合:

  • 复杂的数据处理流程
  • 机器学习等迭代计算
  • 实时数据处理
  • 交互式数据分析
  • 图计算

总结

MapReduce 和 Spark 的关系可以概括为:Spark 是 MapReduce 思想的进化和扩展。虽然 Spark 在大多数场景下已经取代了 MapReduce,但 MapReduce 的核心概念仍然是分布式计算的基础,理解 MapReduce 有助于更好地掌握 Spark 的工作原理。

MapReduce 的"Map-Shuffle-Reduce"模式在 Spark 中依然存在,但 Spark 通过内存计算和更灵活的执行模型大大提升了性能和易用性。