MapReduce
HDFS 读操作
HDFS 写(文件上传)操作
MapReduce:
MapReduce的设计目标是可以对一组顺序组织的数据元素/记录进行处理。
MapReduce提供了以下的主要功能:
数据划分和计算任务调度:系统自动将一个作业(Job)待处理的大数据划分为很多个数据块,每个数据块对应于一个计算任务(Task),并自动调度计算节点来处理相应的数据块。作业和任务调度功能主要负责分配和调度计算节点(Map节点或Reduce节点),同时负责监控这些节点的执行状态,并负责Map节点执行的同步控制。
数据/代码互定位:为了减少数据通信,一个基本原则是本地化数据处理,即一个计算节点尽可能处理其本地磁盘上所分布存储的数据,这实现了代码向数据的迁移;当无法进行这种本地化数据处理时,再寻找其他可用节点并将数据从网络上传送给该节点(数据向代码迁移),但将尽可能从数据所在的本地机架上寻 找可用节点以减少通信延迟。
系统优化:为了减少数据通信开销,中间结果数据进入Reduce节点前会进行一定的合并处理;一个Reduce节点所处理的数据可能会来自多个Map节点,为了避免Reduce计算阶段发生数据相关性,Map节点输出的中间结果需使用一定的策略进行适当的划分处理,保证相关性数据发送到同一个Reduce节点;此外,系统还进行一些计算性能优化处理,如对最慢的计算任务采用多备份执行、选最快完成者作为结果。
出错检测和恢复:以低端商用服务器构成的大规模MapReduce计算集群中,节点硬件(主机、磁盘、内存等)出错和软件出错是常态,因此 MapReduce需要能检测并隔离出错节点,并调度分配新的节点接管出错节点的计算任务。同时,系统还将维护数据存储的可靠性,用多备份冗余存储机制提 高数据存储的可靠性,并能及时检测和恢复出错的数据
原理:
Mr application master 分配和控制maptask
MR中的四个独立实体
- 客户端(client): 编写mapreduce程序,配置作业,提交作业,这就是程序员完成的工作。
- JobTracker: 初始化作业,分配作业,与TaskTracker通信,协调整个作业的执行。
- TaskTracker: 保持与JobTracker的通信,在分配的数据片段上执行Map或Reduce任务,TaskTracker和JobTracker的不同有个很重要的方面,就是在执行任务时候TaskTracker可以有n多个,JobTracker则只会有一个。
- Hdfs: 保存作业的数据、配置信息等等,最后的结果也是保存在hdfs上面。
流程角度运作机制详解:
- 客户端编写好mapreduce程序,配置好mapreduce的作业(也就是job)。
- 提交job到JobTracker上。
- JobTracker分配一个新的job任务的ID值;检查输出目录是否存在,如果不存在就抛出错误给客户端;检查输入目录是否存在,如果不存在同样抛出错误;根据输入计算输入分片(Input Split),如果分片计算不出来也会抛出错误。
- 以上检查都通过,JobTracker就会配置Job需要的资源。
- JobTracker初始化作业,将Job放入一个内部的队列,让配置好的作业调度器能调度到这个作业。
- 作业调度器初始化job,创建一个正在运行的job对象(封装任务和记录信息),以便JobTracker跟踪job的状态和进程。
- 作业调度器获取输入分片信息(input split),每个分片创建一个map任务。
- tasktracker运行一个简单的循环机制定期发送心跳给jobtracker(间隔五秒,可配置),心跳是jobtracker和tasktracker沟通的桥梁,通过心跳,jobtracker可以监控tasktracker是否存活,也可以获取tasktracker处理的状态和问题,同时tasktracker也可以通过心跳里的返回值获取jobtracker给它的操作指令。
- 分片执行任务,在任务执行时候jobtracker可以通过心跳机制监控tasktracker的状态和进度,同时也能计算出整个job的状态和进度,而tasktracker也可以本地监控自己的状态和进度。
- 当jobtracker获得了最后一个完成指定任务的tasktracker操作成功的通知时候,jobtracker会把整个job状态置为成功。
- 然后当客户端查询job运行状态时候(异步操作),客户端会查到job完成的通知的,任务执行完成。
- 如果job中途失败,mapreduce也会有相应机制处理,一般而言如果不是程序本身有bug,mapreduce错误处理机制都能保证提交的job能正常完成,如果是程序本身bug,任务在重复执行2~3次后,会结束执行,jobtracker会把整个job状态置为失败。
内置数据类型:
-
BooleanWritable:标准布尔型数值
-
ByteWritable:单字节数值
-
DoubleWritable:双字节数值
-
FloatWritable:浮点数
-
IntWritable:整型数
-
LongWritable:长整型数
-
Text:使用UTF8格式存储的文本
-
NullWritable:当<key, value>中的key或value为空时使用
MapReduce使用:
Map阶段
package com.hadoop.mr;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import com.sun.xml.bind.v2.runtime.unmarshaller.XsiNilLoader.Array;
/**
* 该类是让maptask来调用的
* @author Administrator
* 继承了一个父类,里面有四个泛型参数,读数据的公共操作都封装好了,
* 它会读取数据后传送到这个程序中,以key,value的形式进行数据传递
* KEYIN:默认情况下,是mr框架所读取到的一行文本的起始偏移量,Long
* VALUEIN:默认情况下,是mr框架所读的一行文本的内容 类型是String
* KEYOUT;用户自定义逻辑处理完以后输出的数据中的key :指的是单词 String
* VALUEOUT:用户自定义逻辑处理完之后输出的数据的value :单词出现的次数 1 Long
* 无论是KEYIN还是KEYOUT都要经过网络传输,所以必须经过序列化
* 在hadoop中有一个更精简的序列化接口,Long要写出LongWritable
* 本质上还是Long,只是实现了hadoop的序列化接口。String序列化成Text
*/
public class WordcountMap extends Mapper<LongWritable, Text, Text,LongWritable >{
/**
* 业务逻辑要在该方法中编写,重写map
* maptask会对每一行输入数据调用我们自定义的map方法
* Key:一行的偏移量
* value:一行的内容
* 实际业务:将文件内容中一行按照空格切割单词
* 此时Key没有太大用处,value是一行的内容对我们有用
*/
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//拿到一行内容
//将maptask传给我们的文本内容现转换为String
String line = value.toString();
//根据空格将这一行内容切分成单词
String[] words = line.split("");
/**
* 遍历统计次数
* 此时有两种思路:1.把统计好一行的单词封装成map("单词","次数")
* 2.把统计好的一行的任意个单词都将次数写为1,相同的也写成1
* 第一种思路是无法完全统计的,因为有好多行,统计起来比较麻烦
* 第二种思路是,只要出现一次单词就计数为1,等到reduce阶段再去做统计
* 因为reduce的统计是有范围的,所以一定范围内相同的单词计数为1
* 再进行叠加即可。相同的单词会到一个文件中去。
*/
//将单词输出为<单词,1>
for (String word : words) {
/**写出单词直接用mr框架提供的上下文对象 随后maptask分发数据的时候是
* 按照单词分发的,所有key应该是单词,相同的单词会发给相同的reducetask
*/
context.write(new Text(word), new LongWritable(1));
}
}
// public static void main(String[] args) {
// String line="asdgag";
// String[] split = line.split("");
// System.out.println(split[0]);
// }
}
Reduce阶段
package com.hadoop.mr;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
* 该类是让reducetask来调用的,reducetask程序在框架中写好 KEYIN VALUEIN:与mapper输出的KEYOUT
* VALUEOUT类型对应 所以KEYIN应该是单词,类型Text VALUEIN对应的是输出单词的次数 1 LongWritable(1)
* KEYOUT,VALUEOUT 是自定义reducer逻辑处理结果的输出类型 那么reduce输出的结果是单词和它的总次数 KEYOUT是单词 类型
* Text VALUEOUT 是总次数 LongWritable
*/
public class WordcountReduce extends Reducer<Text, LongWritable, Text, LongWritable> {
/**
* key:是一组相同单词kv对的key,这个是maptask分发的结果
* 比如<hello,1><hello,1><hello,1><hello,1>
* 但是reducetask是有范围的,还有可能是<a,1><a,1><a,1><a,1>
* reduce为了统计单词的个数,在调用这个方法时应该把相同的单词
* 作为一组来调用一次
* key:hello value:1+1+1+1.....所以第二个参数是迭代器
* 是用来迭代结果。
* reducetask就可以统计一个单词的总次数了。
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context)
throws IOException, InterruptedException {
//计数,对单个单词进行合并计数
Long count=(long) 0;
for (LongWritable value : values) {
count+=value.get();
}
//将一个单词和它出现的次数写进context
context.write(key,new LongWritable(count));
}
}
Run阶段
package com.hadoop.mr;
import org.apache.hadoop.conf.Configuration;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/**
* 写好的map与reduce程序,需要一个启动的主类
* 然后打包到每个节点上进行启动
* 相当于yarn集群的客户端,在此封装相关参数
* 指定jar包,最后交给yarn分配资源
* */
public class WordcountDriver {
public static void main(String[] args) throws Exception {
//封装成一个job对象(封装了mapreduce的相关参数)
Configuration cfg = new Configuration();
/**
* cfg不用设置参数的原因
* 将mr程序发布到Linux中,Linux中节点都有hadoop配置,
* 到时会自动读取cfg.set("mapreduce.framwork.name","yarn")....
*/
//这里导的是org.apache.hadoop.mapreduce.Job;
//mapred jar包已过时
Job job = Job.getInstance(cfg);
//指定本程序的jar包缩在的本地路径
job.setJarByClass(WordcountDriver.class);
//指定本业务job使用的mapper业务类
job.setMapperClass(WordcountMap.class);
job.setReducerClass(WordcountReduce.class);
//指明mapper和reduce的输出key/value类型
//一定要注意Text的类型 apache.hadoop.io.Text
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
//指定job的输出结果所在的目录
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// job.submit() 提交
//true代表的是:要把集群返回的信息打印出来看
//completion:表示集群运行成功还是失败
boolean completion = job.waitForCompletion(true);
System.out.println(completion?0:1);
}
}
在Hadoop上运行
(1)将mapreduce程序打包成jar文件
(2)将打包好的jar文件传到你hadoop集群的机器上(我的hadoop集群是装在linux虚拟机中的)用SSH把jar传过去
(3)创建一个要测试的文件,上传到/wordcount1/input目录中
(4)在HADOOP上运行该MapReduce程序
hadoop jar hadoop.jar com.zhiyou.hadoop.mr.WordCountDriver
/wordcount1/input /wordcount1/output
hadoop.jar:从eclipse中导出的jar包
com.zhao.hadoop.mr.WordCountDriver :jar包中的主程序所在的类
/wordcount1/input:输入的文件路径
/wordcount1/output :输出的文件路径
(5)在/wordcount1/output中查看第二个文件,查看运行结果