Hadoop 中的大数据技术:MapReduce(1)
第1章 MapReduce 概述
1.1 MapReduce 定义
MapReduce 是一个用于分布式计算的编程框架,它是 Hadoop 核心组件之一,主要用于开发分布式数据分析应用。
MapReduce 的主要功能是将用户编写的业务逻辑代码与默认组件相结合,形成一个完整的分布式计算程序,并能够在 Hadoop 集群上并发执行。
1.2 MapReduce 优缺点
1.2.1 优点
- 易于编程:用户只需要实现几个简单的接口就能完成分布式程序的开发,这使得 MapReduce 成为了非常流行的编程模型。
- 良好的扩展性:可以通过简单地增加硬件资源来提升计算能力。
- 高容错性:设计时考虑到了运行在廉价硬件上的情况,因此具备自动故障恢复的能力。
- 适合大规模数据处理:支持PB级别的数据量处理,可利用上千台服务器集群进行并行计算。
1.2.2 缺点
- 不擅长实时计算:响应时间通常较长,不适合需要快速返回结果的应用场景。
- 不擅长流式计算:输入数据集必须是静态的,不适合持续更新的数据流处理。
- 不擅长 DAG 计算:多个任务之间存在依赖时,中间结果频繁写入磁盘会导致性能下降。
1.3 MapReduce 核心思想
- 程序分为两个阶段:Map 和 Reduce 阶段。
- Map 阶段:多个 MapTask 实例并行运行,处理数据。
- Reduce 阶段:多个 ReduceTask 实例处理来自所有 MapTask 的输出。
- 单个 MapReduce 作业:只包含一个 Map 阶段和一个 Reduce 阶段;对于复杂的业务逻辑,可能需要多个 MapReduce 作业串行执行。
1.4 MapReduce 进程
- MrAppMaster:负责整个 MapReduce 程序的任务调度和状态协调。
- MapTask:负责 Map 阶段的数据处理。
- ReduceTask:负责 Reduce 阶段的数据处理。
1.5 WordCount 示例详解
1.5.1 环境准备
- 创建 Maven 工程:MapReduceDemo
- 添加依赖:在
pom.xml
文件中加入 Hadoop 和其他必要依赖。 - 配置日志:在
src/main/resources/log4j.properties
中设置日志级别和输出格式。 - 创建 包:
com.lzl.mapreduce.wordcount
1.5.2 编写程序
Mapper 类
package com.lzl.mapreduce.wordcount;
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
protected 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);
context.write(word, ONE);
}
}
}
Reducer 类
package com.lzl.mapreduce.wordcount;
import .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
protected 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);
}
}
Driver 类
package com.lzl.mapreduce.wordcount;
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 WordCountDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCountDriver.class);
job.setMapperClass(WordCountMapper.class);
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);
}
}
1.5.3 测试
本地测试
- 配置 Hadoop 环境:确保 Hadoop 环境变量已正确设置。
- 运行程序:在 IDE 中运行 WordCountDriver 类。
集群测试
-
打包:使用 Maven 打包工具将程序打包成包含依赖的 JAR 文件。
<build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
-
上传 JAR 文件:将 JAR 文件上传至集群指定目录。
-
启动集群:使用脚本启动 Hadoop 集群。
-
执行程序:使用
hadoop jar
命令提交作业。[lzl@hadoop12 hadoop-3.1.3]$ sbin/start-dfs.sh [lzl@hadoop13 hadoop-3.1.3]$ sbin/start-yarn.sh [lzl@hadoop12 hadoop-3.1.3]$ hadoop jar wc.jar com.lzl.mapreduce.wordcount.WordCountDriver /user/lzl/input /user/lzl/output
第2章 Hadoop序列化
2.1 序列化概述
2.1.1 什么是序列化
- 定义:序列化是指将内存中的对象转换为字节序列的过程,以便于存储或在网络中传输。
- 反序列化:将接收到的字节序列还原为内存中的对象。
2.1.2 为什么需要序列化
- 存储和传输:序列化允许将“活的”对象存储到磁盘或发送到远程计算机。
- 提高效率:序列化可以减少存储空间和网络传输的开销。
2.1.3 为什么不使用原生序列化
- 效率问题:的序列化机制较为臃肿,包含了很多不必要的元数据,不适合高性能的网络传输。
- Hadoop自定义序列化:Hadoop采用了更轻量级的序列化机制(Writable接口),以提高效率和兼容性。
2.1.4 Hadoop序列化的特点
- 紧凑性:高效利用存储空间。
- 高速度:低开销的读写操作。
- 互操作性:支持多种语言间的交互。
2.2 自定义bean对象实现序列化接口(Writable)
2.2.1 实现步骤
- 实现Writable接口。
- 提供无参构造函数:用于反序列化时的反射调用。
- 重写序列化方法:
write()
方法用于序列化。 - 重写反序列化方法:
readFields()
方法用于反序列化。 - 保持序列化和反序列化顺序一致。
- 重写
toString()
方法:便于输出结果。 - 实现
Comparable
接口:如果bean对象需要作为key使用,则需要能够排序。
2.2.2 示例代码
流量统计Bean对象
package com.lzl.mapreduce.writable;
import org.apache.hadoop.io.Writable;
import .io.DataInput;
import .io.DataOutput;
import .io.IOException;
public class FlowBean implements Writable {
private long upFlow; // 上行流量
private long downFlow; // 下行流量
private long sumFlow; // 总流量
public FlowBean() {}
public long getUpFlow() {
return upFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public long getDownFlow() {
return downFlow;
}
public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
public void setSumFlow() {
this.sumFlow = this.upFlow + this.downFlow;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.upFlow = dataInput.readLong();
this.downFlow = dataInput.readLong();
this.sumFlow = dataInput.readLong();
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
}
Mapper类
package com.lzl.mapreduce.writable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import .io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
private Text outK = new Text();
private FlowBean outV = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] fields = line.split("\t");
String phone = fields[1];
String up = fields[fields.length - 3];
String down = fields[fields.length - 2];
outK.set(phone);
outV.setUpFlow(Long.parseLong(up));
outV.setDownFlow(Long.parseLong(down));
outV.setSumFlow();
context.write(outK, outV);
}
}
Reducer类
package com.lzl.mapreduce.writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import .io.IOException;
public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
private FlowBean outV = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
long totalUp = 0;
long totalDown = 0;
for (FlowBean flowBean : values) {
totalUp += flowBean.getUpFlow();
totalDown += flowBean.getDownFlow();
}
outV.setUpFlow(totalUp);
outV.setDownFlow(totalDown);
outV.setSumFlow();
context.write(key, outV);
}
}
Driver驱动类
package com.lzl.mapreduce.writable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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;
import .io.IOException;
public class FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(FlowDriver.class);
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
boolean success = job.waitForCompletion(true);
System.exit(success ? 0 : 1);
}
}
感谢您阅读完这篇文章,如果您觉得有所收获,别忘了关注我的公众号[大数据深度洞察],更多精彩内容等您来探索!”
“希望本文能对您有所帮助,如果您还想获取更多类似的优质内容,欢迎关注我的公众号[大数据深度洞察],让我们一起在技术的海洋中遨游!”
“如果您喜欢这篇文章的内容,期待在公众号[大数据深度洞察]中与您有更多的交流,那里有更多深度的技术文章和实用的技巧等着您!