MapReduce
MapReduce主要包括:map(映射)负责处理原始数据生成中间结果,reduce(归约)处理map输出中中间结果生成最终结果。
主要处理map与reduce的业务代码,map与reduce之间的shuffle(核心)过程,尤其关注key-value的设置
优势:MapReduce,多个节点同时运行效率高,数据处理时间较为均衡。
Map过程
-
-
物理存储128M(除了最后一块)
-
-
split
-
切片
-
定义每个map任务的数据处理量
-
默认切片大小与block大小相同
-
切片过小,mapstak开启过多;切片过大,节点空暇,利用率低下
-
-
-
maptask
-
一个split(切片)对应一个maptask,maptask只处理切片的原始数据
-
maptask根据用户的代码执行作业
-
maptask默认每次读取并处理一行数据,得到临时处理结果value
-
可手动定义key-value读取器
-
-
临时结果存在内存中,通过kvbuffer存入,同时还提交了比较器
-
-
kvbuffer
-
maptask的结果输出目标
-
内存中的环形空间,默认100M
-
通过溢写实现循环的数据写入(maptask的行处理结果)和数据写出(将临时结果存入硬盘)
-
溢写的默认阈值为80%
-
-
spill
-
溢写:kvbuffer默认设置80%的阈值,达到阈值时将kvbuffer中旧数据写入硬盘,同时写入新数据将已溢写出的内存区域覆盖,实现kvbuffer循环数据写入。
-
调整80%的阈值,实现新数据存入速度与溢写数据到硬盘的均衡,否则新数据必须等待旧数据溢写完成
-
每次溢写输出80M数据,数据大小与阈值关联
-
溢写时根据map输入的比较器或默认比较器生成输出的每个数据的key
-
-
partation
-
将溢写的数据进行分区,分区的数量与reduce数量相同
-
根据每条数据的key对数据分区(分区比较器)
-
-
sort
-
在溢写和分区全部完成后,对分区内的数据基于key进行排序(getOutputKeyComparator)
-
默认使用快速排序法,排序基于key的内置比较器
-
-
merge
-
使用归并算法,合并执行了分区排序的数据,默认根据分区的先后排列
-
归并算法还是基于key的内置比较器进行排序
-
Reduce过程
-
fetch
-
拉取数据,从每个mapertask节点拉取reduce所需的数据
-
-
merge
-
将所有fetch的数据,根据key进行归并排序,生成一个文件。
-
归并排序规则是基于key对象内置比较器(getOutputKeyComparator)
-
-
reducetask
-
分组比较器将相同的key的数据(所有临时处理数据)拉取到一个reducetask中处理
-
分组比较器将相同key(这是比较器认为的相同)的数据排到一起
-
每个reducetask生成最终的key与value
-
-
output
-
reducetask将结果输出到hdfs,避免结果文件过大问题
-
源码分析
map源码
reduce源码
MR的1.0与2.0
1.0过程
-
Client
-
客户端执行提交命令
-
hadoop jar wordcount.jar com.shsxt.ly.WordCountJob
-
-
命令提交到JobTacker中
-
-
JobTacker
-
JobTacker与每个节点的taskTrcker保持心跳
-
监控各节点的资源状态(CPU,IO)
-
jobTracker接收到任务后,将任务分配给(就近,空闲)的节点执行Task
-
任务开始后,JobTacker监控TaskTracker的任务进度,在其他节点任务完成后,若还存在执行缓慢的TaskTracker,将task任务拷贝到其他节点执行,两个节点同时执行,采纳第一个完成的结果数据。
-
-
TaskTracker
-
各节点各具备一个TaskTracker,监控所在节点资源(CPU IO 硬盘)
-
与JobTacker保持心跳
-
接收JobTacker的Task,TaskTracker分配节点资源给Task
-
上述资源的大小单位为slot槽,slot的大小是固定的
-
-
Task
-
分为maptask与reducetask
-
关于1.0的缺陷
-
单JobTacker,任务量过大容易宕机,易产生单点故障
-
JobTacker内存无法扩充
-
MR耦合度过高,只服务MR,对其他计算框架支持差
2.0过程
引入了yarn技术也就是ResourceManager与ApplicationMaster分别执行资源监控以及任务执行。
-
Client
-
ResourceManager
-
资源管理集群
可以使用主备节点架构
-
可将Container与ApplicationMaster视为由ResourceManager生成
-
-
NodeManager
-
负责本节点的资源管理
-
实时与ResourceManager心跳汇报
-
使得ResourceManger能够知道各节点的资源状态
-
-
Container
-
从ResourceManager申请资源
-
可以实现资源动态调整,任务查出申请资源额度则kill该task
-
-
ApplicationMaster
-
一个MR任务对应的主节点
-
ResourceManager将ApplicationMaster随机分配到任意节点上
-
负责应用程序相关的事务:任务调度、任务监控、容错
-
将任务生成Task,分配到各节点上
-
task执行完毕,向ResourceManager发送信号
-
ResourceManger去kill当前对象
-
ResourceManger回收Container
-
-
-
Task
-
实际任务执行者
-
每个task中存有对应ApplicationTask的地址
-
分为maptask与reducetask
-
maptask的数量与split数量一致
-
reduce需要手动指定,数量要少于节点数
-
结果存放在hdfs上,避免容量不可控
-
MR集群搭建
基于hadoop2.x的HA集群搭建,需要hadoop与zookeeper关闭。
配置文件
目录/opt/sxt/hadoop-2.6.5/etc/hadoop
-
配置mapred-site.xml
-
cp mapred-site.xml.template mapred-site.xml
-
vim mapred-site.xml
配置yarn框架
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
-
-
配置yarn-site.xml
-
vim yarn-site.xml
-
yarn.resourcemanager.cluster-id配置yurn的集群名
-
yarn.resourcemanager.ha.rm-ids配置Reduce的主备Reduce(yurn)节点
-
yarn.resourcemanager.hostname.xxx配置Reduce各节点
-
yarn.resourcemanager.zk-address配置Map节点,与DN对应一致
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>mr_shsxt</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>node3</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>node1</value>
</property>
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>node1:2181,node2:2181,node3:2181</value>
</property>
-
-
将配置文件拷贝到其他节点
scp mapred-site.xml yarn-site.xml root@node3:`pwd`
节点启动
各节点 zkServer.sh start 启动Zookeeper
主节点 start-all.sh 启动DFS与主yarn节点
备用yarn节点 yarn-daemon.sh start resourcemanager
jsp查看节点启动状态
DFSZKFailoverController
NameNode
JournalNode
DataNode
ResourceManager RS节点(reduce)主备
NodeManager NM节点(map)对应DN
QuorumPeerMain
Jps
访问节点是:RS节点主机:8088
MR计算实现
1 依赖导入
导入hadoop121个相关jar
导入hadoop配置文件core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml到conf资源文件夹中
2 Job类
在main方法中写入配置信息
-
job名称,主类,Mapper类,Reduce类,Reduce数量,数据源路径,结果存放路径
-
mapper临时结果的key与value类型
-
分区比较器,分组比较器
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WeatherJob {
public static void main(String[] args) throws Exception {
//获取配置文件
Configuration configuration = new Configuration(true);
//本地模式运行
configuration.set("mapreduce.framework.name", "local");
//创建JOB
Job job = Job.getInstance(configuration);
//设置主Class
job.setJarByClass(WeatherJob.class);
//设置Job的名称
job.setJobName("xxx");
//设置JOB的Reduce数量
job.setNumReduceTasks(2);
//设置JOB的输入路径
FileInputFormat.setInputPaths(job, new Path("/shsxt/java/weather"));
//设置Job的输出路径
FileOutputFormat.setOutputPath(job, new Path("/shsxt/java/weather_result_" + System.currentTimeMillis()));
//设置Job-Mapper的输出key类型
job.setMapOutputKeyClass(Weather.class);
//设置Job-Mapper的输出value类型
job.setMapOutputValueClass(IntWritable.class);
//设置分区器
job.setPartitionerClass(WeatherPartitioner.class);
//设置分组比较器
job.setGroupingComparatorClass(WeatherGroupingComparator.class);
//设置Job的Mapper
job.setMapperClass(WeatherMapper.class);
//设置Job的Reduce
job.setReducerClass(WeatherReducer.class);
//等待Job的完成
job.waitForCompletion(true);
}
}
job说明
-
job可以通过内部类直接包含map与reduce
map与reduce均为静态内部类
-
定义K-V行读取方式
job.setInputFormatClass(KeyValueTextInputFormat.class)
KeyValueTextInputFormat是基于制表符(/t)分隔行数据,第一个 /t 之前的数据为key,之后为value;若行数据中无/t,默认行数据为key,value为空。
若不设置默认与普通行读取器
-
基于配置文件的全局数据存取
存:conf.set("key","value") 或setInt(key,2)...
取:context.getConfiguration().getInt("key", 1) 后一个参数为空值时的默认值
将数据存到conf配置中,可以存入多种类型的数据,使用对应的get,set方法进行存取
-
全局累加
-
设置job的enum内部类作为累加计数器
//枚举元素为my
public static enum Mycounter { my } -
存值(在map与reduce)
-
context.getCounter(Mycounter.my).increment(xxx);
-
xxx就是累加到原先my中的值
-
累加时需要取整
-
-
对于循环job,每次job完成,累加计数器会重置
-
取值
-
job.getCounters().findCounter(Mycounter.my).getValue()
-
结果返回了一个long数值
-
-
job循环
-
循环在main方法中,可设置在while(true)死循环内
-
循环跳出方式:
-
在每次job执行后,等待waitForCompletion返回的执行结果true
-
执行比较判断,跳出值与计算中产生的特定值比较,满足条件break
-
-
循环外的配置项
-
获取配置文件Configuration conf = new Configuration(true);
-
项目执行方式:本地和跨平台
-
设置循环结束的指标,也就是上述的跳出值
-
-
循环内的配置项
-
迭代的次数计数器 i++(在循环外设置int i=0)
-
基础配置:当前job,主类,mapper,reduce,map输出的中间K-V的类型
-
迭代的任务名与数据输入输出路径
-
迭代的任务名job.setJobName("pagerank" + i)
-
迭代的结果输出路径
FileOutputFormat.setOutputPath(job, new Path("/output/xxx" + (i - 1)))
-
迭代的数据输入路径
配置第一迭代的数据输入路径(原始文件)
FileInputFormat.addInputPath(job, new Path("/input"))
配置第二次以后迭代输入路径,前一次计算结果作为后一次计算的输入
FileInputFormat.addInputPath(job, new Path("/output/xxx" + i))
-
最终效果为:第i次循环,数据来源路径为/output/xxx(i-1),计算结果存放路径为/output/xxx(i-1)
-
-
等待单次job结束,并执行业务判断是否结束迭代循环
boolean flag = job.waitForCompletion(true);
//等待当前循环结束
boolean flag = job.waitForCompletion(true);
if (flag) {
//执行业务判断(例:获取枚举计数器my的值)
long sum = job.getCounters().findCounter(Mycounter.my).getValue();
if (sum < xxx) {
//满足条件跳出while循环
break;
}
}
-
3 Mapper类
-
XxxMapper的传入参数为:
LongWriter行偏移量,Text行数据类型,临时结果key类型,临时结果value类型
-
map方法的参数为:
key行偏移量,vlaue行数据,context上下文容器
在map处理行数据,生成key与value的结果,并将行数据写出
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 XxxMapper extends Mapper<LongWriter,Text,Xxx,IntWritable>{
map端合并
在job中设置:job.setCombinerClass(xxxReduce.class),该类与reudce基本相同
处理指定的切片
//获取切片
FileSplit fs = (FileSplit) context.getInputSplit();
//判断切片所在的文件,是否是包含xxx路径文件的数据
//若包含则处理切片
//若是取反,则表示不处理包含某个文件数据的切片
if (fs.getPath().getName().contains("xxx")) {
//这里执行map代码
}
4 Reducer类
-
XxxReducer的传入参数依次为:
-
Xxx临时结果的key类型
-
IntWritable临时结果的value类型
-
Text最终结果的key类型
-
IntWritable最终结果的value类型
-
-
reduce方法的参数依次为:临时结果的key,key所对应的所有临时结果values,上下文context
在map方法中
-
通过迭代器获取每个临时结果的value与对应的key
-
Iterator<IntWritable> iterator = values.iterator();
-
while (iterator.hasNext()) { iterator.next() } 中, iterator.next()为临时value值,key为临时key值
-
key具体值可能不相同,只是分组比较器将其识别为形同的key分到一个reduce中处理
-
-
通过上下文context输出
-
context.writer(最终key,最终value)
-
在一个reduce中context输出多次
-
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.ReduceTask;
import org.apache.hadoop.mapreduce.Reducer;
public class WeatherReducer extends Reducer<Weather, IntWritable, Text, IntWritable> {
5 hadoop数据包装类型
内部自带比较器
-
Text文本类型
toString()转为字符串
-
LongWritable和IntWritable表示整形数字,DoubleWritable表示浮点小数
上述包装类型的get方法返回对应的类型的数值,get()-->int/long/double
java数值或String通过带参构造转为包装类:new Text("参数")
6 关于key自定类
-
实现WritableComparable<>接口,范型传入自身
-
内部写入若干自定义字段
-
实现接口的三个方法
-
write(定义序列化的数据)
out.writeInt(this.year); 定义字段以何种数据类型写入硬盘
-
readFields(定义反序列化的数据)
this.year=in.readInt(); 定义字段以何种格式从硬盘输出到内存
-
注:write与readFields的字段的顺序和类型必须一致
-
compareTo(定义比较方法,实现基于key的排序比较)
使用字段进行多重比较,借助字段本身的compareTo方法实现比较
-
-
set,get,toString,hashCode方法
import org.apache.hadoop.io.WritableComparable;
public class Weather implements WritableComparable<Weather>{
private Integer year;
private Integer month;
private Integer day;
private Integer temperature;
//写出数据,序列化
7 分区比较器
控制自定义相同的key进入同一个分区(reduce节点)
对于自定类,将需要处于同一分组的各字段构建出数字,该数字对分区数(reduces数)取余。
-
继承Partitioner<>传入需要比较的key与value类型
-
重写getPartition方法,其中numPartitions就是分区数
import org.apache.hadoop.mapreduce.Partitioner;
public class WeatherPartitioner extends Partitioner<Weather, IntWritable>{
控制某些key独享一个分区reduce,使用如下代码
public class FirstPartition extends HashPartitioner<Text, IntWritable> {
public int getPartition(Text key, IntWritable value, int reduceCount) {
//if中执行逻辑判断,使得满足条件的key进入
if (key.equals(new Text("xxx"))) {
//指定分配,由于reduceCount个数从0计数,直接分配的最后一个reduce分区
return reduceCount - 1;
//可以多个条件,reduce值从大到小分配对应的key
} else {
//若并非指定的key,则使用父类的默认分区器,平分剩余的reduce分区
//注意:reduceCount需要减去已分配的分区,避免冲突
return super.getPartition(key, value, reduceCount - 1);
}
}
}
8 分组比较器
控制自定义相同的key进入同一个分组(reducetask)
对于自定义类,将需要处于同一reducetask的字段进行比较
-
继承WritableComparator类
-
构造器中传入自定义类
-
重写public int compare(WritableComparable a, WritableComparable b)方法
其中a和b两个比较的对象,需要强转为自定义类
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class WeatherGroupingComparator extends WritableComparator{
//构造器传入自定义类
public WeatherGroupingComparator() {
super(Weather.class ,true);
}
//重写比较器
9 运行及打包
-
打包方式
项目右键Export,选择JAR file ,选择打包项目,JAR file中选取导出路径。
将jar文件放入linux节点中,运行命令:hadoop jar xxx.jar com.jay.XxxJob (指定jar文件与主类)
-
本地运行
job类中加入配置:configuration.set("mapreduce.framework.name", "local");
右键-run-hadoop
-
跨平台运行
conf.set("mapreduce.app-submission.corss-paltform", "true");
conf.set("mapreduce.framework.name", "local");
10 数据拉取到本地进行计算
-
job配置:
job.addCacheFile(new Path("/xxx/xxx").toUri()); 将hdfs的数据载入缓存
-
map及reduce中的使用
重写setup方法,该方法是另一个前置方法,且只执行一次
//获取所有缓存文件的路径数组
URI[] cacheFiles = context.getCacheFiles();
//依次遍历缓存文件
for (int i = 0; i < cacheFiles.length; i++) {
//获取缓存文件的名字
URI uri = cacheFiles[i];
// 获取制定的文件
if (uri.getPath().endsWith("xxx文件名")) {
//定义文件路径
Path path = new Path(uri.getPath());
//获取字符流
BufferedReader br = new BufferedReader(new FileReader(path.getName()));
String line =null;
String alldata=null;
//循环一行一行读取,最后拼接起来
while ((line = br.readLine()) != null) {
String alldata=line+"";//可以使用其他方式
}
br.close();
}
}