MapReduce 与 Spark 的关系
基本关系
MapReduce 和 Spark 是两代大数据处理框架:
- MapReduce:Google 2004年提出的分布式计算模型,Hadoop实现了开源版本
- Spark:UC Berkeley 2010年开发的分布式计算引擎,可视为MapReduce的进化版
Spark 继承了 MapReduce 的核心思想(数据分区、移动计算而非数据),但通过内存计算和更丰富的API显著提升了性能和易用性。
核心区别
| 特性 | MapReduce | Spark |
|---|---|---|
| 数据存储 | 磁盘为主 | 内存为主 |
| 处理速度 | 较慢 | 比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 执行流程:
- Map:将文本分割为单词,输出
(单词, 1)的键值对 - 写入磁盘:Map输出结果写入本地磁盘
- Shuffle:将相同单词的记录发送到同一个Reducer
- 读取磁盘:Reducer从磁盘读取数据
- Reduce:统计每个单词的出现次数
- 写入结果:将结果写入HDFS
Spark 执行流程:
- 创建RDD:从文件创建初始RDD
- 转换操作:
flatMap分割文本为单词map转换为(单词, 1)的键值对reduceByKey聚合相同单词的计数
- 执行计划:Spark创建DAG(有向无环图)并优化执行计划
- 执行:数据尽可能保留在内存中处理
- 输出结果:触发行动操作,将结果写入存储系统
为什么Spark更高效?
- 内存计算:中间结果保留在内存中,减少I/O开销
- 惰性求值:转换操作不立即执行,等到行动操作时才执行,可优化整体计划
- DAG引擎:构建完整的执行计划,优化任务调度和数据流
- 更丰富的API:不限于Map和Reduce,提供更多高级操作
使用场景
MapReduce 适合:
- 简单的批处理作业
- 数据量大但计算相对简单的场景
- 内存资源有限的环境
Spark 适合:
- 复杂的数据处理流程
- 机器学习等迭代计算
- 实时数据处理
- 交互式数据分析
- 图计算
总结
MapReduce 和 Spark 的关系可以概括为:Spark 是 MapReduce 思想的进化和扩展。虽然 Spark 在大多数场景下已经取代了 MapReduce,但 MapReduce 的核心概念仍然是分布式计算的基础,理解 MapReduce 有助于更好地掌握 Spark 的工作原理。
MapReduce 的"Map-Shuffle-Reduce"模式在 Spark 中依然存在,但 Spark 通过内存计算和更灵活的执行模型大大提升了性能和易用性。