MapReduce介绍:
MapReduce思想在生活中处处可见,或多或少都曾接触过这种思想。Mapreduce的思想核心是分而治之,使用于大量复杂
的任务处理场景(大规模数据处理场景)。
Map复杂分,即把复杂的任务分解为若干个简单的任务,来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此
间几乎没有依赖关系。
Reduce负责合,即对map阶段的结果进行全局汇总。
MapReduce运行在yarn集群:
1.resourceManager
2.NodeManager
两个阶段合起来正式MapReduce思想的体现。
还有一个比较形象的语言解释MapReduce:
我们要数图书馆的所有书,你数1号书架,我数2号书架。这就是Map。我们人越多,数书就更快。
现在我们到了一起,把所有人的统计数加在一起,这就是reduce。
MapReduce设计思想:
MapReduce是一个分布式运算程序的编程框架,核心功能将用户编写的业务逻辑代码和自带默认组件整合成一个完整的
分布式运算程序。并发运行在Hadoop集群上。
MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统层面的处理细节。为程序员提供一抽象和高层
的编程接口和框架,程序员仅需要关系应用层的具体计算问题,仅编写少量的处理应用本身计算问题的程序代码。如何
具体完成这个并行计算任务所相关的诸多系统层细节被隐藏起来,交给计算框架去处理:
Map和Reduce为程序员提供了一个清晰的操作接口抽象描述。MapReduce中定义了如下的Map和Reduce两个抽象的编程
接口,由用户去编程实现,Map和Reduce,MapReduce处理的数据类型是《key,value》键值对。
Map: (k1,v1) ---> [(k2,v2)]
Reduce:(k2,[v2])---> [(k3,v3)]
一个完整的MapReduce程序在分布式运行时有三类实例进程:
- MRAppMaster负责整个程序的过程调度及状态协调
- MapTask 负责map阶段的整个数据处理流程
- ReduceTask负责reduce阶段的整个数据处理流程
MapReduce编程规范:
MapReduce的开发一共有八个步骤,其中Map阶段分为2个步骤,Shuffle阶段4个步骤,Reduce阶段分为2个步骤:
Map阶段2个步骤:
- 设置InputFormat类,将数据切分为key-value (k1和v1)对,输入到第二步
- 自定义Map逻辑,将第一步的结果装换成另外Key-Value(k2和V2)对,输出结果
Shuffle阶段4个步骤:
3.对输出的Key-value对进行分区
4.对不同分区的数据按照相同的key排序
5.(可选)对分组过的数据初步规约,降低数据的网络拷贝
6.对数据进行分组,相同key的Value放入一个集合中
Reduce阶段2个步骤:
7.对多个Map任务的结果进行排序以及合并,编写Reduce函数实现自己的逻辑,对输入的key-value进行处理,转为新的Key-Value(k3和V3)输出
8.设置OutputFormat处理并保存Reduce输出的key-Value数据
WordCount:
需求:在一堆给定的文本文件中统计输出每一个单词出现的总次数
数据准备:
cd /export/servers
vim wordcount.txt
向其中放入一下内容并保存:
hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop
上传到HDFS:
hdfs dfs -mkdir /wordcount/
hdfs dfs -put wordcount.txt /wordcount/
Mapper编写:
/**
*
* KEYIN : K1 的类型
* VALUEIN: V1的类型
*
* KEYOUT: K2的类型
* VALUEOUT: V2的类型
*/
public class WordCountMapper extends Mapper<LongWritable, Text,Text,LongWritable> {
/**
*key: K1 行偏移量
* value: V1 每一行的文本数据
* context : 表示上下文对象
**/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1.将一行的文本数据进行拆分
String line = value.toString();
String[] split = line.split(",");
Text text = new Text();
LongWritable longWritable = new LongWritable();
for (String word : split) {
// 2.遍历数组,组装K2 ,V2
text.set(word);
longWritable.set(1);
// 3.将K2 和V2 写入上下文中
context.write(text,longWritable);
}
}
}
Reducer编写:
/**
* 四个泛型解释:
*
* KEYIN: K2 类型
* VALUEIN: V2 类型
*
* KEYOUT: K3 类型
* VALUEOUT: V3 类型
*
*/
public class WordCountReducer extends Reducer<Text, LongWritable,Text,LongWritable> {
// reduce 方法作用: 将新的K2和V2 转为 K3 和V3 将K3 和V3 写入上下文中
/**
*
*
**/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
long count = 0;
// 1. 遍历集合,将集合中的数字相加得到V3
for (LongWritable value : values) {
count += value.get();
}
// 2.将k3 和V3 写入上下文:
context.write(key,new LongWritable(count));
}
}
定义主类,描述Job并提交Job:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.net.URI;
public class JobMain extends Configured implements Tool {
// 该方法用于指定一个job任务
@Override
public int run(String[] strings) throws Exception {
//1.创建一个job任务对象
Job job = Job.getInstance(super.getConf(), "wordCount");
// 如果打包运行出错,则需要加该配置
job.setJarByClass(JobMain.class);
// 2.配置job任务对象的八个步骤
// 第一步: 指定文件的读取方式和读取路径
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://192.168.154.100:8020/wordcount"));
// 第二步: 设置我们的mapper类
job.setMapperClass(WordCountMapper.class);
// 设置我们map阶段完成之后的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
// 第三步 四, 五,六 省略
// 第七部: 设置我们的reduce类
job.setReducerClass(WordCountReducer.class);
// 设置我们reduce阶段完成之后的输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//第八部: 设置输出类以及输出路径
job.setOutputFormatClass(TextOutputFormat.class);
Path path = new Path("hdfs://192.168.154.100:8020/word_count");
TextOutputFormat.setOutputPath(job, path);
// 增加目录是否存在判断,不用每次修改输出目录
// 获取FileSystem
FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.154.100:8020"), new Configuration());
boolean exists = fileSystem.exists(path);
if (exists) {
//删除目标目录
fileSystem.delete(path, true);
}
// 等待任务结束
boolean b = job.waitForCompletion(true);
return b ? 0 : 1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
// 启动job任务
int run = ToolRunner.run(configuration, new JobMain(), args);
System.exit(run);
}
}
如果遇到以下错误:
Caused by:
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.AccessCon
trolException): Permission denied: user=admin, access=WRITE,
inode="/":root:supergroup:drwxr-xr-x
直接将hdfs-site.xml当中的权限关闭即可<property>
<name>dfs.permissions</name>
<value>false</value>
</property>
本地运行完成之后,就可以打成jar包放到服务器上面去运行了,实际工作当中,都是将代码
打成jar包,开发main方法作为程序的入口,然后放到集群上面去运行
MapReduce运行模式:
本地运行模式:
1. MapReduce 程序是被提交给 LocalJobRunner 在本地以单进程的形式运行
2. 处理的数据及输出结果可以在本地文件系统, 也可以在hdfs上
3. 怎样实现本地运行? 写一个程序, 不要带集群的配置文件, 本质是程序的 conf 中是否有
mapreduce.framework.name=local 以及
yarn.resourcemanager.hostname=local 参数
4. 本地模式非常便于进行业务逻辑的 Debug , 只要在 Eclipse 中打断点即可configuration.set("mapreduce.framework.name","local");
configuration.set(" yarn.resourcemanager.hostname","local");
TextInputFormat.addInputPath(job,new Path("file:///F:\\wordcount\\input"));
TextOutputFormat.setOutputPath(job,new Path("file:///F:\\wordcount\\output"));
集群运行模式:
- 将MapReduce程序提交给Yarn集群,分发到很多的节点上并发执行
- 处理的数据和输出结果应该位于HDFS文件系统
- 提交集群的实现步骤:将程序打成JAR包,然后在集群的任意一个节点上用hadoop命名启动:
hadoop jar original-hadoop_api-1.0-SNAPSHOT.jar com.hadoop.api.mapreduce.JobMain
本地运行模式:
1. MapReduce 程序是在本地以单进程的形式运行
2. 处理的数据及输出结果在本地文件系统
TextInputFormat.addInputPath(job,new
Path("file:///E:\\mapreduce\\input"));
TextOutputFormat.setOutputPath(job,new
Path("file:///E:\\mapreduce\\output"));
MapReduce分区:
分区概述:
在MapReduce中,通过我们指定分区,会将同一个分区的数据发送到同一个Reduce当中进行处理,
例如: 为了数据的统计,可以把一批类似的数据发送到同一个Reduce当中,在同一个Reduce当中统计相同类型的数据,就可以
实现类似的数据分区和统计等。
其实就是相同类似的数据,有共性的数据,送到一起去处理
Reduce当中默认的分区只有一个
需求:将以下数据进行分开处理:
详细数据参见partition.csv 这个文本文件,其中第五个字段表示开奖结果数值,现在需求将15
以上的结果以及15以下的结果进行分开成两个文件进行保存
文件地址:
链接:https://pan.baidu.com/s/1KzJdHx9UbbJNxbIPpglBcg
提取码:zuw5
复制这段内容后打开百度网盘手机App,操作更方便哦
分区步骤:
Step1.定义Mapper:
这个Mapper程序不多任何逻辑,也不对Key-Value做任何改变,只是接受数据,然后往下发送:
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @Title: PartitionMapper
* @Description: TODO
* K1: 行偏移量 LongWritable
* V1: 行文本数据 Text
*
* K2: 行文本数据 Text
* V2: NullWritable
*
*/
public class PartitionMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
// map方法将K1和V1转为K2和V2
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
context.write(value,NullWritable.get());
}
}
Step2.自定义Partitioner
主要逻辑就是这里,这也就是这个案例的意义,通过Partitioner将数据分发给不同的Reducer:
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* @Title: MyPartitioner
*/
public class MyPartitioner extends Partitioner<Text, NullWritable> {
/**
* 1.定义分区规则
* 2.返回对应的分区编号
**/
@Override
public int getPartition(Text text, NullWritable nullWritable, int i) {
// 1.拆分行文本数据K2 ,获取中奖字段的值
String[] split = text.toString().split("\t");
String numStr = split[5];
//2.判断中奖字段的值与15的关系,然后返回对应的分区编号
if(Integer.parseInt(numStr) > 15){
return 1;
}else{
return 0;
}
}
}
Step3.定义Reducer逻辑:
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @Title: PartitionReducer
* @Description: TODO
* K2 Text
* V2: Nullwritable
*
* K3: Text
* V3:Nullwritable
*/
public class PartitionReducer extends Reducer<Text, NullWritable,Text,NullWritable> {
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
Step4主类,中设置分区类,和ReduceTask个数:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
/**
* @Title: PartitionMain
* @Description: TODO
*/
public class PartitionMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
// 1. 创建一个job任务对象
Job job = Job.getInstance(super.getConf(), "partition_mapreduce");
//2.对job任务进行配置(八个步骤)
// 第一步: 设置输入类和输入的路径
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://192.168.154.100:8020/partitioner"));
// 第二步:设置Mapper类和数据类型(K2和V2)
job.setMapperClass(PartitionMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
// 第三步,指定分区类
job.setPartitionerClass(MyPartitioner.class);
// 第,四,五,六步 默认
// 第七部 指定Reducer类和数据类型(K3和V3)
job.setReducerClass(PartitionReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 设置reduceTask的个数
job.setNumReduceTasks(2);
// 第八部:指定输出类和输出路径:
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job, new Path("hdfs://192.168.154.100:8020/partitioner_out"));
// 3.等到任务结束
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
// 启动一个job任务:
int run = ToolRunner.run(configuration, new PartitionMain(), args);
System.exit(run);
}
}
MapReduce中的计数器:
计数器是收集作业统计信息的有效手段之一,用于质量控制或应用级统计。计数器还可辅助
诊断系统故障。如果需要将日志信息传输到 map 或 reduce 任务, 更好的方法通常是看能否
用一个计数器值来记录某一特定事件的发生。对于大型分布式作业而言,使用计数器更为方
便。除了因为获取计数器值比输出日志更方便,还有根据计数器值统计特定事件的发生次数
要比分析一堆日志文件容易得多
hadoop内置计数器列表:
所有的这些都是MapReduce的计数器的功能,既然MapReduce当中有计数器的功能,我们如
何实现自己的计数器???
第一种方式
第一种方式定义计数器,通过context上下文对象可以获取我们的计数器,进行记录 通过
context上下文对象,在map端使用计数器进行统计
/**
* @Title: PartitionMapper
* @Description: TODO
* K1: 行偏移量 LongWritable
* V1: 行文本数据 Text
*
* K2: 行文本数据 Text
* V2: NullWritable
*
*/
public class PartitionMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
// map方法将K1和V1转为K2和V2
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 每次执行该方法,加1
Counter counter = context.getCounter("MR_COUNT", "MyRecordCount");
counter.increment(1L);
context.write(value,NullWritable.get());
}
}
第二种方式
通过enum枚举类型来定义计数器 统计reduce端数据的输入的key有多少个:
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @Title: PartitionMapper
* @Description: TODO
* K1: 行偏移量 LongWritable
* V1: 行文本数据 Text
*
* K2: 行文本数据 Text
* V2: NullWritable
*
*/
public class PartitionMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
public static enum Counter{
MY_REDUCE_INPUT_RECORDS,MY_REDUCE_INPUT_BYTES
}
// map方法将K1和V1转为K2和V2
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
context.getCounter(Counter.MY_REDUCE_INPUT_RECORDS).increment(1L);
context.write(value,NullWritable.get());
}
}