目录
Hadoop第0部分:分布式存储计算平台及Hadoop入门
Hadoop第一部分:HDFS的构架和使用
Hadoop第二部分:MapReudce(一)
Hadoop第二部分:MapReudce(二)
Hadoop第二部分:MapReudce(三)
本文项目地址:https://github.com/KingBobTitan/hadoop.git
MapReudce(一)
一、回顾hdfs
- 应用场景
- 一次写入,多次读取,不适于修改的业务场景
- 特点
- 分块机制:用于实现HDFS的分布式
- 副本机制:用于实现HDFS数据的安全性保障机制
- 默认会将每个块存储3份
- HDFS上的文件是逻辑的概念,真正的物理存储是块
- 当数据写入HDFS时,由DataNode 来实现副本的同步
- HDFS读写流程
- 写
- 客户端提交写请求给Namenode,Namenode验证请求,接受请求
- 客户端分块,将 第一个块请求给Namenode写入
- Namenode返回这个块要写入的三个地址
- 客户端通过机架感知选择最近的DataNode,提交第一个块的写入
- 最近的DataNode要将 数据同步给其他的DataNode
- 直到整个文件写入完成
- Namenode记录元数据,元数据的更改写入edits
- 读
- 客户端提交读请求给Namenode,Namenode根据元数据返回该文件所有块的地址
- 客户端会根据机架感知从每个块的 列表中获取每个块最近的机器上获取每个块的数据
- 将所有块进行合并
- 写
- HDFS客户端操作
- 数据
- hdfs dfs
- -ls
- -get
- -put
- -rm -r
- -mkdir -p
- 管理
- dfsadmin:集群管理操作
- report
- refreshNodes
- haadmin
- fsck:文件系统检查
- balance:负载均衡
- dfsadmin:集群管理操作
- 数据
- 数据安全性
- 数据安全
- HDFS的数据安全:副本
- 数据存储目录单独挂载在数据盘
- Linux的操作系统安全
- Raid:磁盘冗余阵列
- Raid0:插入两块硬盘,每个1TB,做了raid0,在操作系统中只能看到1块2TB,负载均衡
- Raid1:插入两块硬盘,每个1TB,做了raid1,在操作系统中只能看到1块1TB,高可用
- 空间减少1半
- Raid5:插入三块硬盘,每个1TB,做了raid1,在操作系统中只能看到1块2TB
- Safe mode:
- 当HDFS启动时,会自动进入安全模式
- 等待所有DataNode进行汇报,与元数据进行比较,来保证数据是否丢失恢复
- 当发现数据丢失,当前所有存在的块/元数据中记录的块 <= 99.9%,会停留在安全模式,实现数据恢复,直到大于99.9%,会自动退出
- 安全模式下不允许写
- 会报错:safe mode is on
- 手动管理命令:hdfs dfsadmin
hdfs dfsadmin -safemode <enter | leave | get | wait>
- 当HDFS启动时,会自动进入安全模式
- HDFS的数据安全:副本
- 数据安全
- HDFS JAVA API
- 首先要构建hdfs对象
- 所有的Hadoop程序必须要有一个Configuration对象
- 用于管理整个程序所有的配置
- 第一步先从环境变量中加载所有的*-default.xml
- 第二步先从环境变量中加载所有的*-site.xml
- 自定义属性配置:conf.set(key,value)
- 调用对应的方法即可
- 所有分布式存储框架的读写一般都是通过分布式计算框架来读写
- 几乎所有的分布式计算框架都封装了读写HDFS的接口
- MapReduce:FileInputFormat:默认会从HDFS读取数据
- setInputPath(new Path(“hdfs_path”))
- Spark:调用了Hadoop的类
- sc.textInput(“hdfs_path”)
- MapReduce:FileInputFormat:默认会从HDFS读取数据
- 二次开发:基于HDFS API做产品
- 首先要构建hdfs对象
二、课程目标
- Hadoop的HA及Federation
- HA:高可用机制,避免单点问题
- Federation:负载均衡
- MapReudce编程模型
- input map shuffle reduce output
- Map Reduce
- MapReduce编程规则
- 开发接口
- MapReduce实现WordCount
- 自己开发
- MapReduce实现各地区二手房个数统计
- 类似于统计类业务
- MapReduce编程模板
- 分区与自定义分区
- shuffle的初探
三、Hadoop的HA及Federation
1、故障转移以及负载均衡
- 故障转移:failover
- 某个节点发生故障,这个节点的功能由别的节点实现
- 两个或以上节点,功能都一模一样,但是只有一个是工作的
- 负载均衡:load_balance
- 两个或以上节点,功能都一模一样,但是所有节点一起工作
2、HA:高可用
- HDFS
- NameNode:NameNode只有一个,如果宕机,整个HDFS不可用,存在单点故障
- 接客
- 管理
- 元数据
- DataNode:如果datanode某台宕机,不影响业务,其他datanode继续工作,并且数据会被恢复到别的机器
- NameNode:NameNode只有一个,如果宕机,整个HDFS不可用,存在单点故障
- YARN
- ResourceManager:存在单点故障
- 接客
- 管理
- 资源管理和任务调度
- NodeManger:如果NM宕机,不影响业务,程序会在别的NM中启动并执行
- ResourceManager:存在单点故障
- HA:实现两个NameNode以及两个ResourceManager
- Hadoop0.x:没有HA架构,只有单节点
- Hadoop2.2+:支持HA,但只支持两个
- Hadoop3.x:支持多个
- 两个节点的状态
- Active:正在工作
- StandBy:备份【不工作】
- 如果Active故障,StandBy会转换为Active状态,接替原来的节点
3、Federation
- Hadoop2.x:增加的一个特性,HDFS特有的联盟机制
- 本质:实现NameNode负载均衡
- 两个NameNode,一起工作
- 应用场景:非常大的公司,想将所有数据放在一个大的集群,但是需要按照业务拆分元数据
- 每个NameNode专门负责一部分业务
- 联盟机制不能与HA机制共存,如果非要共存,构建多个nameservice
4、Hadoop HA的实现
-
NameNode HA实现的原理【YARN的HA实现原理类似】
- 问题:
- 有两个NameNode的情况下,DataNode向谁注册?
- 监听每个DataNode的状态,NameNode会返回给客户端
- 都注册,因为任何一个NameNode都可能成为Active,每个NN都要监听DN
- 有两个NameNode的情况下,Client向哪个NN提交请求?
- 向ActiveNameNode请求
- 如何知道谁是Active
- HDFS将所有的NameNode进行封装成一个整体NameSpace:mycluster
- 所有客户端启动时会读取配置文件,将请求发送给NameSpace,由NameSpace负责转发给Active
- 有两个NameNode的情况下,如何实现主备的切换?
- zookeeper:存储所有主备的信息,并监听active主节点
- zkfc:每个zkfc负责监听一个NameNode
- 注册:用于NameNode在zookeeper中进行注册
- 监听状态:实现切换
- 如何保证两个NameNode在切换以后元数据保持一致?
- journalnode:统一的edits日志存储服务
- active的负责请求journalnode将自己的元数据变化写入journalnode
- standby的负责请求journalnode从journalnode中读取edits数据,并对自己的元数据进行操作,保证与active是一致的
- 有两个NameNode的情况下,DataNode向谁注册?
- 问题:
-
实现的架构
-
Hadoop HA的搭建配置
- HDFS:https://hadoop.apache.org/docs/r2.6.5/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html#Configuration_overview
- YARN:https://hadoop.apache.org/docs/r2.6.5/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html
- 节点的划分
进程 node01 node02 node03 namenode namenode【active】 namenode【standby】 datanode * * * journalnode * * * zkfc * * zookeeper * * * resourcemanager rm[standby] rm[ative] nodemanager * * * - 具体配置实现:参考其他
- HA集群管理命令
- 获取每个NameNode的状态
hdfs haadmin -getServiceState nn1 hdfs haadmin -getServiceState nn2
-
如果配置了自动切换的属性,不允许使用手动切换的命令
- 手动的切换命令
hdfs haadmin -transitionToActive nn1 --forceactive hdfs haadmin -transitionToStandby nn2
- 配置
<!--开启自动切换--> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property> #该配置开启,手动命令无法执行
- 手动的切换命令
-
如果要在自动模式下进行切换
hdfs haadmin -failover nn2 nn1 nn2:当前是active nn1:要将nn1转成active
-
YARN的HA的管理命令
yarn rmadmin -transitionToActive <serviceId> [--forceactive] -transitionToStandby <serviceId> -failover [--forcefence] [--forceactive] <serviceId> <serviceId> -getServiceState <serviceId>
问题:
- 当前配置了HA以后
- 以后任何的HDFS的客户端,如果想要访问hdfs,都要加载core-site和hdfs-site中所有的配置
- 以后如果涉及到其他框架要访问HDFS的HA,将core-site.xml和hdfs-site.xml,拷贝到对应的配置文件目录下即可
- idea:resource
- flume:conf
MapReudce编程模型
1、MapReduce设计思想
- 最初作为一个分布式计算框架,负责实现分布式的计算
- 主:JobTracker
- 从:TaskTracker
- 资源管理和任务调度
- 启动所有进程,以及运行所有的Task进程
- Hadoop2:引进了YARN:负责资源管理和任务调度
- MapReduce:整个计算模型的开发
- 思想:分而治之
- 将一个大的计算任务,进行拆分,交给每个节点来处理一个小的任务
- 每个小的任务负责计算得到一个结果
- 最后所有的小结果会合并返回一个结果
- 例子:1+……+9
- maptask1:1+2+3 6
- maptask2:4+5+6 15
- maptask3:7+8+9 24
- reduceTask:6+15+24 = 45
- 任务类型:
- MapTask:大任务拆分以后的每个小的任务
- ReduceTask:最后合并的任务
2、MapReduce五大阶段
-
逻辑过程:MapReduce中所有的数据都是以keyvalue形式存在
-
Input:负责整个程序的输入
-
默认读取HDFS上的文件
由一个属性从哪里读取数据:mapreduce.job.inputformat.class job.setInputFormat():Java中设置用于指定从哪里读取数据 默认该属性的值:TextInputFormat.class 功能:从hdfs上读取数据,并且将HDFS上读取到的内容变成keyvalue格式 文件中一行会变成一个 keyvalue key:行的偏移量 value:行的内容 DBInputFormat:用于读取数据库 如果没有提供需要的接口,怎么办? 提供了抽象类接口,允许自己实现
-
例如:读取wordcount的文件
hadoop hive spark hue hadoop hive || TextInputFormat加载这个数据,返回KeyValue格式 || key value 0 hadoop hive spark 18 hue hadoop hive
-
-
将返回的KeyValue传递给下一个阶段
-
-
Map:分,启动多个MapTask,每个MapTask处理一部分数据
-
每个MapTask处理的数据不同,但处理逻辑是一模一样
-
处理逻辑:自定义一个方法:map
#每个Maptask对自己负责的每一条数据就会调用一次map方法 public void map (key,value,上下文){ #数据的处理逻辑,自定义 #输出 }
-
Map阶段结束的输出的数据还是KeyValue
-
-
Shuffle:洗牌,默认实现的功能
-
输入:
key value
-
分区:有几个reduce就有几个分区
- 分区决定了当前这条数据会进入哪个Reduce,被哪个ReduceTask进行处理
-
排序
- 默认按照key进行字典顺序排序
-
分组
- 按照key进行分组,每一种key只有一条,相同key的value会放入同一个迭代器
-
输出:
key value[Itertor]
-
-
Reduce:合,启动ReduceTask对每一条/每一个key的数据进行处理,默认情况下,reduce只有1个
-
每一种key或者每一条keyvalue会调用一次reduce方法
-
处理逻辑:reduce
public void reduce(key,Itertor[value],上下文){ #处理逻辑 #输出 }
-
输出
key value
-
-
Output:负责将上一步的结果进行输出
- 默认输出规则:将结果保存到HDFS上变成文件,并且key和value之间以制表符分隔
-
-
物理程序
- MapTask:input+map+map端的shuffle
- ReduceTask:reduce端的shuffle+reduce+output
3、WordCount整个过程
Input
输入:/wordcount/input
【可以指定目录或者直接指定文件,如果指定目录,目录中不能包含目录】
hadoop hive hbase
spark hive
hadoop hive
||
处理:InputFormat => FileInputForamt => TextInputFormat
key:行在文件中的偏移量
value:行的内容
||
输出:key value
0 hadoop hive hbase
10 spark hive
20 hadoop hive
Map
处理:map方法决定了每条数据的处理逻辑
以每个单词作为key
value全部为1
伪代码:key value
String[] words = value.split(" ")
for(word : words){
key = word
value = 1
}
||
输出:key value
hadoop 1
hive 1
hbase 1
spark 1
hive 1
hadoop 1
hive 1
Shuffle
处理:分区、排序、分组
排序:
hadoop 1
hadoop 1
hbase 1
hive 1
hive 1
hive 1
spark 1
分组:
hadoop 1,1
hbase 1
hive 1,1,1
spark 1
||
输出:key value
hadoop 1,1
hbase 1
hive 1,1,1
spark 1
Reduce
处理:reduce方法决定每条数据的处理逻辑
伪代码:key,itertor<values>
for(value : values)
sum + = value
||
输出:key value
hadoop 2
hbase 1
hive 3
spark 1
Output:
输出:保存到hdfs上
MapReduce编程规则
1、Driver类
- 驱动类:包含main方法
- 推荐:继承Configured ,实现接口Tool
2、Input类
- 默认的Input类:TextInputFormat.class
- 所有的输入类:都要继承自InputFormat
3、Mapper类
- 负责Map阶段的任务,包含map方法
- 必须继承Mapper
4、Reducer类
- 负责Reduce阶段的任务逻辑,包含reduce方法
- 必须继承Reducer类
5、Output类
- 默认的Output类:TextOutputFormat.class -> FileOutputFormat
- 所有的输出类必须继承OutputFormat类
6、数据结构
- 整个MapReduce中所有的数据都以KeyValue的形式存在
7、数据类型
- Java中的类型:int / long /double/boolean/null /String
- Hadoop中所有类型必须为序列化类型
- IntWritable
- LongWritable
- DoubleWritable
- BooleanWritable
- NullWritable
- Text
MapReduce实现WordCount
1、需求分析以及五大阶段过程
Input
输入:/wordcount/input
【可以指定目录或者直接指定文件,如果指定目录,目录中不能包含目录】
hadoop hive hbase
spark hive
hadoop hive
||
处理:InputFormat => FileInputForamt => TextInputFormat
key:行在文件中的偏移量
value:行的内容
||
输出:key value
0 hadoop hive hbase
10 spark hive
20 hadoop hive
Map
处理:map方法决定了每条数据的处理逻辑
以每个单词作为key
value全部为1
伪代码:key value
String[] words = value.split(" ")
for(word : words){
key = word
value = 1
}
||
输出:key value
hadoop 1
hive 1
hbase 1
spark 1
hive 1
hadoop 1
hive 1
Shuffle
处理:分区、排序、分组
排序:
hadoop 1
hadoop 1
hbase 1
hive 1
hive 1
hive 1
spark 1
分组:
hadoop 1,1
hbase 1
hive 1,1,1
spark 1
||
输出:key value
hadoop 1,1
hbase 1
hive 1,1,1
spark 1
Reduce
处理:reduce方法决定每条数据的处理逻辑
伪代码:key,itertor<values>
for(value : values)
sum + = value
||
输出:key value
hadoop 2
hbase 1
hive 3
spark 1
Output:
输出:保存到hdfs上
2、代码实现
public class WordCount extends Configured implements Tool {
/**
* 构建一个MapReduce程序,配置程序,提交程序
* @param args
* @return
* @throws Exception
*/
@Override
public int run(String[] args) throws Exception {
/**
* 第一:构造一个MapReduce Job
*/
//构造一个job对象
Job job = Job.getInstance(this.getConf(),"mrword");
//设置job运行的类
job.setJarByClass(WordCount.class);
/**
* 第二:配置job
*/
//input:设置输入的类以及输入路径
// job.setInputFormatClass(TextInputFormat.class); 这是默认的
Path inputPath = new Path(args[0]);//以程序的第一个参数作为输入路径
TextInputFormat.setInputPaths(job,inputPath);
//map
job.setMapperClass(WordCountMapper.class);//指定Mapper的类
job.setMapOutputKeyClass(Text.class);//指定map输出的key的类型
job.setMapOutputValueClass(IntWritable.class);//指定map输出的value的类型
//shuffle
//reduce
job.setReducerClass(WordCountReduce.class);//指定reduce的类
job.setOutputKeyClass(Text.class);//指定reduce输出的 key类型
job.setOutputValueClass(IntWritable.class);//指定reduce输出的value类型
// job.setNumReduceTasks(1);//这是默认的
//output
// job.setOutputFormatClass(TextOutputFormat.class);//这是默认的输出类
Path outputPath = new Path(args[1]);//用程序的第二个参数作为输出路径
//如果输出目录已存在,就删除
FileSystem hdfs = FileSystem.get(this.getConf());
if(hdfs.exists(outputPath)){
hdfs.delete(outputPath,true);
}
//设置输出的地址
TextOutputFormat.setOutputPath(job,outputPath);
/**
* 第三:提交job
*/
//提交job运行,并返回boolean值,成功返回true,失败返回false
return job.waitForCompletion(true) ? 0 : -1;
}
/**
* 整个程序的入口,负责调用当前类的run方法
* @param args
*/
public static void main(String[] args) {
//构造一个conf对象,用于管理当前程序的所有配置
Configuration conf = new Configuration();
try {
//调用当前类的run方法
int status = ToolRunner.run(conf, new WordCount(), args);
//根据程序运行的 结果退出
System.exit(status);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Mapper的类,实现四个泛型,inputkey,inputValue,outputKey,outputValue
* 输入的泛型:由输入的类决定:TextInputFormat:Longwritable Text
* 输出的泛型:由代码逻辑决定:Text,IntWritable
* 重写map方法
*/
public static class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
//构造用于输出的key和value
private Text outputKey = new Text();
private IntWritable outputValue = new IntWritable(1);
/**
* map方法:Input传递过来的每一个keyvalue会调用一次map方法
* @param key:当前的 key
* @param value:当前的value
* @param context:上下文,负责将新的keyvalue输出
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//将每一行的内容转换为String
String line = value.toString();
//对每一行的内容分割
String[] words = line.split(" ");
//迭代取出每个单词
for(String word:words){
//将单词赋值给key
this.outputKey.set(word);
//输出
context.write(this.outputKey,this.outputValue);
}
}
}
/**
* 所有的Reduce都需要实现四个泛型
* 输入的keyvalue:就是Map的 输出的keyvalue类型
* 输出的keyvalue:由代码逻辑决定
* 重写reduce方法
*/
public static class WordCountReduce extends Reducer<Text, IntWritable,Text, IntWritable>{
private IntWritable outputValue = new IntWritable();
/**
* reduce方法 ,每一个keyvalue,会调用一次reduce方法
* @param key:传进来的key
* @param values:迭代器,当前key的所有value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//取出迭代器的值进行累加
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
//封装成输出的value
this.outputValue.set(sum);
//输出每一个key的结果
context.write(key,outputValue);
}
}
}
3、Windows上运行Hadoop程序
- 第一步:在windows上创建一个不包含中文路径的目录
- 例如:c:\\hadoop
- 第二步:在windows上配置一个HADOOP_HOME
- HADOOP_HOME=c:\\hadoop
- 第三步:将提供的bin文件夹放入c:\\hadoop目录下
- 第四步:在path环境变量中添加c:\\hadoop\bin
- 第五步:测试,是否成功,进入cmd,输入hadoop
- 只要提示hadoop的用法,就是成功的
- 第六步:重启IDEA,如果不行,就重启电脑
MapReduce实现各地区二手房统计
1、需求分析
-
统计各地区二手房的个数
-
数据格式:
小区名称 户型 面积 地区 楼层 朝向 总价 单价 建造年份
-
过程
- input:读取该文件
- 总共有2万多个keyvalue
- map
- 调用2万多次map方法
- key:地区
- value:1
- shuffle
- 分组
- reduce
- 输入
- key:地区
- value:{1,1,1,1,1,……}
- 输入
- output
- key:地区
- value:个数
- input:读取该文件
-
自己测试新需求:统计各地区的平均单价、最高单价、最低单价
- 结果:地区 平均单价 最高单价 最低单价
2、代码实现
/**
TODO 用于实现Wordcount的MapReduce
*/
public class SecondHouseMR extends Configured implements Tool {
/**
* 构建一个MapReduce程序,配置程序,提交程序
* @param args
* @return
* @throws Exception
*/
@Override
public int run(String[] args) throws Exception {
/**
* 第一:构造一个MapReduce Job
*/
//构造一个job对象
Job job = Job.getInstance(this.getConf(),"mrhouse");
//设置job运行的类
job.setJarByClass(SecondHouseMR.class);
/**
* 第二:配置job
*/
//input:设置输入的类以及输入路径
// job.setInputFormatClass(TextInputFormat.class); 这是默认的
Path inputPath = new Path(args[0]);//以程序的第一个参数作为输入路径
TextInputFormat.setInputPaths(job,inputPath);
//map
job.setMapperClass(HouseMapper.class);//指定Mapper的类
job.setMapOutputKeyClass(Text.class);//指定map输出的key的类型
job.setMapOutputValueClass(IntWritable.class);//指定map输出的value的类型
//shuffle
//reduce
job.setReducerClass(HouseReduce.class);//指定reduce的类
job.setOutputKeyClass(Text.class);//指定reduce输出的 key类型
job.setOutputValueClass(IntWritable.class);//指定reduce输出的value类型
job.setNumReduceTasks(2);//这是默认的
//output
// job.setOutputFormatClass(TextOutputFormat.class);//这是默认的输出类
Path outputPath = new Path(args[1]);//用程序的第二个参数作为输出路径
//如果输出目录已存在,就删除
FileSystem hdfs = FileSystem.get(this.getConf());
if(hdfs.exists(outputPath)){
hdfs.delete(outputPath,true);
}
//设置输出的地址
TextOutputFormat.setOutputPath(job,outputPath);
/**
* 第三:提交job
*/
//提交job运行,并返回boolean值,成功返回true,失败返回false
return job.waitForCompletion(true) ? 0 : -1;
}
/**
* 整个程序的入口,负责调用当前类的run方法
* @param args
*/
public static void main(String[] args) {
//构造一个conf对象,用于管理当前程序的所有配置
Configuration conf = new Configuration();
try {
//调用当前类的run方法
int status = ToolRunner.run(conf, new SecondHouseMR(), args);
//根据程序运行的 结果退出
System.exit(status);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Mapper的类,实现四个泛型,inputkey,inputValue,outputKey,outputValue
* 输入的泛型:由输入的类决定:TextInputFormat:Longwritable Text
* 输出的泛型:由代码逻辑决定:Text,IntWritable
* 重写map方法
*/
public static class HouseMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
//构造用于输出的key和value
private Text outputKey = new Text();
private IntWritable outputValue = new IntWritable(1);
/**
* map方法:Input传递过来的每一个keyvalue会调用一次map方法
* @param key:当前的 key
* @param value:当前的value
* @param context:上下文,负责将新的keyvalue输出
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//将每个二手房的信息中的地区取出来
String region = value.toString().split(",")[3];
this.outputKey.set(region);
//输出当前地区出现一套二手房
context.write(this.outputKey,this.outputValue);
}
}
/**
* 所有的Reduce都需要实现四个泛型
* 输入的keyvalue:就是Map的 输出的keyvalue类型
* 输出的keyvalue:由代码逻辑决定
* 重写reduce方法
*/
public static class HouseReduce extends Reducer<Text, IntWritable,Text, IntWritable>{
private IntWritable outputValue = new IntWritable();
/**
* reduce方法 ,每一个keyvalue,会调用一次reduce方法
* @param key:传进来的key
* @param values:迭代器,当前key的所有value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//取出迭代器的值进行累加
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
//封装成输出的value
this.outputValue.set(sum);
//输出每一个key的结果
context.write(key,outputValue);
}
}
}
MapReduce编程模板
1、Driver类
public class MRModel extends Configured implements Tool {
//负责整个MapReduce任务的初始化
@Override
public int run(String[] args) throws Exception {
/**
* 构建一个job
*/
Job job = Job.getInstance(this.getConf(),"mrjob");
job.setJarByClass(MRModel.class);
/**
* 配置job
*/
//input
TextInputFormat.setInputPaths(job,new Path(args[0]));
//map
job.setMapperClass(MRModelMapper.class);
job.setMapOutputKeyClass(null);
job.setMapOutputValueClass(null);
//shuffle
//reduce
job.setReducerClass(MRModelReduce.class);
job.setOutputKeyClass(null);
job.setOutputValueClass(null);
job.setNumReduceTasks(1);
//output
TextOutputFormat.setOutputPath(job,new Path(args[1]));
/**
* 提交job
*/
return job.waitForCompletion(true) ? 0:-1;
}
//负责程序的入口,以及run方法的调用
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
int status = ToolRunner.run(conf, new MRModel(), args);
System.exit(status);
}
}
2、Mapper类
//用于Map阶段的逻辑处理,包含map方法
public static class MRModelMapper extends Mapper<LongWritable,Text,Text, Text>{
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
super.map(key, value, context);
}
}
3、Reduce类
//用于reduce阶段的处理逻辑,包含reduce方法
public static class MRModelReduce extends Reducer<Text,Text,Text,Text>{
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
super.reduce(key, values, context);
}
}
附录一:Hadoop HA配置文件修改及启动流程
1、拍摄三台虚拟机的快照
-
关闭所有进程
sbin/stop-dfs.sh sbin/stop-yarn.sh
-
关机
- 不关机拍摄的快照比较大,占磁盘空间
-
拍摄快照【用于恢复当前状态,后续学习过程中不用HA】
-
启动三台机器
2、修改配置文件
-
删除三台机器原来的临时目录,重新创建,三台机器都要执行
cd /export/servers/hadoop-2.6.0-cdh5.14.0/ rm -rf hadoopDatas/ #创建hadoop的临时存储目录:存储数据块、fsimage等 mkdir datas #用于journal进程存储edits文件的目录 mkdir journalnode
-
修改第一台机器的配置
-
core-site.xml
<!--配置HDFS的入口地址,即NameNode的地址--> <property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property> <!--配置Hadoop的本地存储位置--> <property> <name>hadoop.tmp.dir</name> <value>/export/servers/hadoop-2.6.0-cdh5.14.0/datas</value> </property> <!--开启hdfs垃圾箱机制,指定垃圾箱中的文件七天之后就彻底删掉单位为分钟--> <property> <name>fs.trash.interval</name> <value>10080</value> </property> <!--指定zookeeper的地址--> <property> <name>ha.zookeeper.quorum</name> <value>node-01:2181,node-02:2181,node-03:2181</value> </property>
-
hdfs-site.xml
<!--关闭hdfs的访问权限--> <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> <!--指定一个HDFS入口的逻辑名称--> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <!--指定HDFS中每个NameNode的逻辑名称--> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <!--指定的两个NameNode具体的rpc地址--> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>node-01:8020</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>node-02:8020</value> </property> <!--指定的两个NameNode具体的http的地址--> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>node-01:50070</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>node-02:50070</value> </property> <!--指定三台journalnode的地址--> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://node-01:8485;node-02:8485;node-03:8485/mycluster</value> </property> <!--指定edits文件具体存在journal机器的什么位置--> <property> <name>dfs.journalnode.edits.dir</name> <value>/export/servers/hadoop-2.6.0-cdh5.14.0/journalnode</value> </property> <!--指定客户端实现HA切换的代理类--> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <!--配置隔离机制--> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/root/.ssh/id_rsa</value> </property> <!--开启自动切换--> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property>
-
yarn-site.xml
<!--配置YARN的HA:开启HA--> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <!--配置YARN的HA:配置统一RM服务名--> <property> <name>yarn.resourcemanager.cluster-id</name> <value>cluster1</value> </property> <!--配置YARN的HA:配置两台RM的id--> <property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property> <!--在node3上配置rm1,在node2上配置rm2--> <!--注意:这个在YARN的另一个机器上一定要修改,非RM的其他机器上不配置此项--> <property> <name>yarn.resourcemanager.ha.id</name> <value>rm1</value> </property> <!--配置YARN的HA:配置两台RM的具体地址--> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>node-03</value> </property> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>node-02</value> </property> <property> <name>yarn.resourcemanager.webapp.address.rm1</name> <value>node-03:8088</value> </property> <property> <name>yarn.resourcemanager.webapp.address.rm2</name> <value>node-02:8088</value> </property> <!--配置YARN的HA:配置Zookeeper的地址--> <property> <name>yarn.resourcemanager.zk-address</name> <value>node-01:2181,node-02:2181,node-03:2181</value> </property> <!--配置YARN的HA:启动自动故障恢复--> <property> <name>yarn.resourcemanager.recovery.enabled</name> <value>true</value> </property> <!--配置YARN的HA:配置容错存储为Zookeeper,默认为文件--> <property> <name>yarn.resourcemanager.store.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value> </property> <!--Yarn上运行程序的类型为MapReduce--> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property>
3、分发并修改
-
分发给第二台和第三台机器
cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop/ scp core-site.xml hdfs-site.xml yarn-site.xml node-02:$PWD scp core-site.xml hdfs-site.xml yarn-site.xml node-03:$PWD
-
修改第一台机器的yarn-site.xml
-
编辑yarn-site.xml
vim yarn-site.xml
-
删除以下内容
<!--在node3上配置rm1,在node2上配置rm2--> <!--注意:这个在YARN的另一个机器上一定要修改,非RM的其他机器上不配置此项--> <property> <name>yarn.resourcemanager.ha.id</name> <value>rm1</value> </property>
-
-
修改第二台机器的yarn-site.xml
-
编辑yarn-site.xml
vim yarn-site.xml
-
更改以下内容:将rm1修改为rm2
<!--在node3上配置rm1,在node2上配置rm2--> <!--注意:这个在YARN的另一个机器上一定要修改,非RM的其他机器上不配置此项--> <property> <name>yarn.resourcemanager.ha.id</name> <value>rm2</value> </property>
-
4、启动测试
-
启动zookeeper
/export/servers/zookeeper-3.4.5-cdh5.14.0/bin/start-zk-all.sh
-
第一次启动需要进行格式化:以后不需要执行
-
启动三台机器的journalnode
cd /export/servers/hadoop-2.6.0-cdh5.14.0/ sbin/hadoop-daemon.sh start journalnode
-
第一台机器的NameNode进行格式化
bin/hdfs namenode -format
-
第一台机器同步元数据到第二台机器
scp -r datas node-02:$PWD
-
关联zookeeper,进行初始化
bin/hdfs zkfc -formatZK
-
三台机器关闭journalnode
sbin/hadoop-daemon.sh stop journalnode
-
-
第一台机器启动HDFS
sbin/start-dfs.sh #会自动启动所有HDFS的进程
-
第三台机器启动YARN
sbin/start-yarn.sh #只启动当前机器的RM和所有的NM
-
第二台机器启动ResourceManager
sbin/yarn-daemon.sh start resourcemanager
附录二:MapReduce编程依赖
<!-- 指定仓库位置,依次为aliyun、cloudera和jboss仓库 -->
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.com/nexus/content/groups/public</url>
</repository>
</repositories>
<properties>
<hadoop.version>2.6.0-cdh5.14.0</hadoop.version>
</properties>
<dependencies>
<!--引入单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Hadoop Client 依赖 -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>