目录
1.extends Mapper类,并重写map(){}方法,实现map的逻辑:
2.extends Reducer类,并重写reduce(){}方法,实现reduce的逻辑:
5.本地已安装Hadoop windoows版,可以直接在本地提交到本地,方便调试
6.通过重写Reduce类cleanup方法,并加载配置参数,控制输出聚合后的输出条数.
7.通过对象实现 继承实现 WritableComparable (序列化) 实现控制(修改)map 对key的排序逻辑,并实现分区控制.
8.Partitioner分区组件:使用实现了compareTo()的对象作为 map key,在默认key.hashCode()分区时不在同一分区.修改分区逻辑.
9.GroupingComparator分组排序组件:使用实现了compareTo()的对象作为 map key,在聚合时,还要修改分组聚合逻辑,以对象id作为key聚合.
10.mapreduce数据倾斜skew--利用Combiner组件 maptask端局部聚合数据来减轻倾斜影响.
12.可以使用内部类一次性实现,Mapper,Reducer, Main(JobSubmitter).复杂逻辑可以通过分步MapReduce实现.
Hadoop之MapReduce原理及Yarn相关
MapReduce简介
MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。主要思想是函数式编程语言中的"Map"和"Reduce"概念,来实现海量数据运算的“分而治之”。
Hadoop-mapreduce是hadoop中一个批量计算的框架,在整个mapreduce作业的过程中,包括从数据的输入,数据的处理,数据的数据输出这些部分,而其中数据的处理部分就要map,reduce,combiner等操作组成。在一个mapreduce的作业中必定会涉及到如下一些组件:
1、客户端,提交mapreduce作业
2、yarn资源管理器,负责集群上计算资源的协调
3、yarn节点管理器,负责启动和监控集群中机器上的计算容器(container)
4、mapreduce的application master,负责协调运行mapreduce的作业
5、hdfs,分布式文件系统,负责与其他实体共享作业文件
自动化调度平台——YARN
hadoop1.x版本JobTracker的作用是资源管理和任务的调度,但不支持多个计算框架。
当存在多个计算框架时,比如说spark,如果两个计算框架都有着自己的资源管理模块,就会存在资源竞争,不便于管理。
此时就需要一个公共的资源管理模块,这就产生了YARN.
hadoop2.x上的mapreduce是基于YARN 的,YARN支持多个计算框架,就比如说刚才说的SPARk.
Yarn上可以支持多个计算框架(MapReduce,spark)
Yarn上的每一个Node Manager 都与每一个dataNode与之对应
Yarn原理过程:
一个客户端A向resource manager 提交请求,resource manager就会会产生一个application master(相当于mapreduce1.x中的jobTracker中的调度模块),resource manager会在hdfs中获取split的信息(被切成了多少块),然后向resource manager申请有多少个mapTask,然后resource manager 会产生多个Container分布在多个节点上(分布在哪个节点上由resource manager控制)
一个客户端B向resource manager 提交请求,resource manager又会会产生一个application master(相当于mapreduce1.x中的jobTracker中的调度模块),resource manager会在hdfs中获取split的信息(被切成了多少块),然后向resource manager申请有多少个mapTask,然后resource manager 会产生多个Container(默认占用空间1个G)分布在多个节点上(分布在哪个节点上由resource manager控制)
Resource manager 又会存在单点故障,此时又需要用到ha来实现高可用性。
YARN集群启动
yarn集群中有两个角色:
主节点:Resource Manager 1台
从节点:Node Manager N台
Resource Manager一般安装在一台专门的机器上
Node Manager应该与HDFS中的data node重叠在一起
Hadoop安装包自带YARN只需配置yarn-site.xml文件即可。
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hdp-03</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>2048</value>
</property>
<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>2</value>
</property>
mapred-site.xml文件中的配置:
<configuration> <!—mapreduce 基于yarn来实现离线计算的,基础开关 -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>
注意:
yarn.nodemanager.resource.memory-mb
默认配置尾8G,最低建议配置2048,
在maptask,reducetask启动之前需要启动application master默认需要占用1536(1.5G)内存,
配置不够时,启动yarn不受影响,但是启动MapReduce时,会报错内存不足
但是实际上在启动后,并不一定会占用1.5g的内存,往往开启是任务极少内存占用极少,随任务累积变大。
yarn.nodemanager.resource.cpu-vcores 逻辑CPU,根据运算能力划分,默认8核
参数配置应参考集群实际硬件配置设置
然后复制到每一台机器上 (以下步骤已实现的可忽略。)
scp /root/apps/hadoop-2.10.0/etc/hadoop/yarn-site.xml master:$(pwd)
然后在hdp-04上,修改hadoop的slaves文件,列入要启动nodemanager的机器
然后将hdp-04到所有机器的免密登陆配置好
然后,就可以用脚本启动yarn集群:
sbin/start-yarn.sh start-yarn.sh
停止:
sbin/stop-yarn.sh stop-yarn.sh
启动完成后,可以在windows上用浏览器访问resourcemanager的web端口:
http://hdp-04:8088
mapreduce Java编程实例:
Hadoop的MapReduce框架已经实现了 map -> shuffle ->reduce 之间的通信过程。
所以我们的代码实现主要有3个:
1.extends Mapper类,并重写map(){}方法,实现map的逻辑:
/**
* KEYIN :是map task读取到的数据的key的类型,是一行的起始偏移量Long
* VALUEIN:是map task读取到的数据的value的类型,是一行的内容String
* KEYOUT:是用户的自定义map方法要返回的结果kv数据的key的类型,在wordcount逻辑中,我们需要返回的是单词String
* VALUEOUT:是用户的自定义map方法要返回的结果kv数据的value的类型,在wordcount逻辑中,我们需要返回的是整数Integer
* 但是,在mapreduce中,map产生的数据需要传输给reduce,需要进行序列化和反序列化,而jdk中的原生序列化机制产生的数据量比较冗余,就会导致数据在mapreduce运行过程中传输效率低下
* 所以,hadoop专门设计了自己的序列化机制,那么,mapreduce中传输的数据类型就必须实现hadoop自己的序列化接口
* hadoop为jdk中的常用基本类型Long String Integer Float等数据类型封住了自己的实现了hadoop序列化接口的类型:LongWritable,Text,IntWritable,FloatWritable
*/
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 切单词
String line = value.toString();
String[] words = line.split(" ");
for(String word:words){
context.write(new Text(word), new IntWritable(1));
}
}
}
2.extends Reducer类,并重写reduce(){}方法,实现reduce的逻辑:
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
int count = 0;
Iterator<IntWritable> iterator = values.iterator();
while (iterator.hasNext()) {
IntWritable value = iterator.next();
count += value.get();
}
context.write(key, new IntWritable(count));
}
}
3.job的客户端程序,提交mapreduce
/**
* 用于提交mapreduce job的客户端程序
* 功能:
* 1、封装本次job运行时所需要的必要参数
* 2、跟yarn进行交互,将mapreduce程序成功的启动、运行
*/
public class JobSubmitter {
public static void main(String[] args) throws Exception {
// 在代码中设置JVM系统参数,用于给job对象来获取访问HDFS的用户身份
System.setProperty("HADOOP_USER_NAME", "root");
Configuration conf = new Configuration();
// 1、设置job运行时要访问的默认文件系统
conf.set("fs.defaultFS", "hdfs://master:9000");
// 2、设置job提交到哪去运行
conf.set("mapreduce.framework.name", "yarn");
conf.set("yarn.resourcemanager.hostname", "hdp-03");
// 3、如果要从windows系统上运行这个job提交客户端程序,则需要加这个跨平台提交的参数
conf.set("mapreduce.app-submission.cross-platform","true");
Job job = Job.getInstance(conf);
// 1、封装参数:jar包所在的位置
job.setJar("d:/wc.jar");
//job.setJarByClass(JobSubmitter.class);
// 2、封装参数: 本次job所要调用的Mapper实现类、Reducer实现类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
// 3、封装参数:本次job的Mapper实现类、Reducer实现类产生的结果数据的key、value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
Path output = new Path("/wordcount/output");
FileSystem fs = FileSystem.get(new URI("hdfs://master:9000"),conf,"root");
if(fs.exists(output)){
fs.delete(output, true);
}
// 4、封装参数:本次job要处理的输入数据集所在路径、最终结果的输出路径
FileInputFormat.setInputPaths(job, new Path("/wordcount/input"));
FileOutputFormat.setOutputPath(job, output); // 注意:输出路径必须不存在
// 5、封装参数:想要启动的reduce task的数量
job.setNumReduceTasks(2);
// 6、提交job给yarn
boolean res = job.waitForCompletion(true);
System.exit(res?0:-1);
}
}
4.在Hadoop集群某节点上提交job
/**
* 如果要在hadoop集群的某台机器上启动这个job提交客户端的话
* conf里面就不需要指定 fs.defaultFS mapreduce.framework.name
* 因为在集群机器上用 hadoop jar xx.jar cn.edu360.mr.wc.JobSubmitterLinuxToYarn 命令来启动客户端main方法时,
* hadoop jar这个命令会将所在机器上的hadoop安装目录中的jar包和配置文件加入到运行时的classpath中
* 那么,我们的客户端main方法中的new Configuration()语句就会加载classpath中的配置文件,自然就有了
* fs.defaultFS 和 mapreduce.framework.name 和 yarn.resourcemanager.hostname 这些参数配置
*/
public class JobSubmitterLinuxToYarn {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://master:9000");
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
// 没指定默认文件系统
// 没指定mapreduce-job提交到哪运行
Job job = Job.getInstance(conf);
job.setJarByClass(JobSubmitterLinuxToYarn.class);
//同上
}
}
5.本地已安装Hadoop windoows版,可以直接在本地提交到本地,方便调试
Configuration conf = new Configuration();
//因本地配置就是默认配置,因此可以不设置参数。
//conf.set("fs.defaultFS", "file:///");
//conf.set("mapreduce.framework.name", "local");
Job job = Job.getInstance(conf);
//其他加载配置参数方法:
//通过加载classpath下的*-site.xml文件解析参数
//Configuration()会默认加载 classpath下的*-site.xml文件
Configuration conf = new Configuration();
//通过加载解析自定义xml配置文件,设置参数
//conf.addResource("xx-oo.xml");
// 通过代码设置参数
//conf.setInt("top.n", 3);
//通过main 函数 运行时传参 设置参数
//conf.setInt("top.n", Integer.parseInt(args[0]));
//通过属性配置文件获取参数
/*Properties props = new Properties();
props.load(JobSubmitter.class.getClassLoader().getResourceAsStream("topn.properties"));
conf.setInt("top.n", Integer.parseInt(props.getProperty("top.n")));*/
6.通过重写Reduce类cleanup方法,并加载配置参数,控制输出聚合后的输出条数.
public class PageTopnReducer extends
Reducer<Text, IntWritable, Text, IntWritable> {
// 使用 自动排序二叉树结构 Map实现类 存储结构 用来存放Map后的数据结果。
// HasMap时无序的,Map的另一个实现了类LinkedHashMap 是按照插入顺序有序的,但不会自动排序
TreeMap<PageCount, Object> treeMap = new TreeMap<>();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Reducer<Text, IntWritable, Text, IntWritable>.Context arg2)
throws IOException, InterruptedException {
int count = 0;
for (IntWritable value : values) {
count += value.get();
}
PageCount pageCount = new PageCount();
pageCount.set(key.toString(), count);
treeMap.put(pageCount, null);
}
/**
* 通过重写cleanup方法,并加载配置参数,控制输出聚合后的输出条数.
*/
@Override
protected void cleanup(Context context) throws IOException,
InterruptedException {
Set<Entry<PageCount,Object>> entrySet = treeMap.entrySet();
Configuration conf = new Configuration();
int topn = conf.getInt("top.n",5);
int i = 0;
for (Entry<PageCount, Object> entry : entrySet) {
context.write(new Text(entry.getKey().getPage()), new IntWritable(entry.getKey().getCount()));
i++;
if(i==topn){
return;
}
}
}
}
7.通过对象实现 继承实现 WritableComparable (序列化) 实现控制(修改)map 对key的排序逻辑,并实现分区控制.
public class OrderBean implements WritableComparable<OrderBean>{
private String orderId;
...
private float amountFee;
public void set(String orderId, String userId, String pdtName, float price, int num){
this.orderId = orderId;
...
this.amountFee = price * num;
}
@Override
public void readFields(DataInput in) throws IOException {
this.orderId = in.readUTF();
...
this.amountFee = this.price * this.num;
}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(this.orderId);
...
out.writeFloat(this.amountFee);
}
/**
* String 的 compareTo() 方法
* this.orderId.compareTo(o.getOrderId()) --> 正序
* o.getOrderId().compareTo(this.orderId) --> 逆序
*/
@Override
public int compareTo(OrderBean o) {
return this.orderId.compareTo(o.getOrderId())==0?Float.compare(o.getAmountFee(), this.amountFee):this.orderId.compareTo(o.getOrderId());
}
}
8.Partitioner分区组件:使用实现了compareTo()的对象作为 map key,在默认key.hashCode()分区时不在同一分区.修改分区逻辑.
public class OrderIdPartitioner extends Partitioner<OrderBean, NullWritable>{
@Override
public int getPartition(OrderBean key, NullWritable v, int numPartitions) {
// 以orderId 分区 分发数据
return (key.getOrderId().hashCode() & Integer.MAX_VALUE) % numPartitions;
}
}
/**
* Hadoop MapReduce中对于默认分区的源码:
* 1. 其中key.hashCode(),是对map输出的key取hashCode值
* 2. & 是java中位运算符,在数据的二进制层面上按位与的意思,两个操作数中位都为1,结果才为1,否则结果为0;
* 3. 综合而言,key.hashCode() & Integer.MAX_VALUE
* 是要保证任何map输出的key在与numReduceTasks取模后决定的分区为正整数。
*/
public int getPartition(K2 key, V2 value, int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
9.GroupingComparator分组排序组件:使用实现了compareTo()的对象作为 map key,在聚合时,还要修改分组聚合逻辑,以对象id作为key聚合.
public class OrderIdGroupingComparator extends WritableComparator{
// 构造方法调用父类构造方法指定比较对象,并创建实例。
public OrderIdGroupingComparator(){
super(OrderBean.class,true);
}
//以下两个方法功能一致
@Override
public int compare(WritableComparable a, WritableComparable b) {
OrderBean o1 = (OrderBean) a;
OrderBean o2 = (OrderBean) b;
return super.compare(o1.getOrderId(), o2.getOrderId());
}
@Override
public int compare(Object a, Object b) {
OrderBean o1 = (OrderBean) a;
OrderBean o2 = (OrderBean) b;
return super.compare(o1.getOrderId(), o2.getOrderId());
}
}
10.mapreduce数据倾斜skew--利用Combiner组件 maptask端局部聚合数据来减轻倾斜影响.
局部聚合实际也是extens Reducer类,如果实际逻辑与 ReduceTask一致,可直接指定ReduceTask的Reducer实现类.
以上3个组件,Partitioner/GroupingComparator/Combiner组件,均需要在Jobsubmitter()中指定.
// 设置分区逻辑类
job.setPartitionerClass(OrderIdPartitioner.class);
// 设置分组排序聚合逻辑类
job.setGroupingComparatorClass(OrderIdGroupingComparator.class);
// 设置maptask端的局部聚合逻辑类
job.setCombinerClass(SkewWordcountReducer.class);
10.1.mapreduce数据倾斜skew的通用解决方案--打散倾斜的key(map阶段给key拼接随机字符串)
第1步map阶段给key拼接随机字符串,打散数据量很大的key:
public static class SkewWordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
Random random = new Random();
int numReduceTasks = 0;
Text k = new Text();
IntWritable v = new IntWritable();
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
numReduceTasks = context.getNumReduceTasks();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws java.io.IOException ,InterruptedException {
String[] words = value.toString().split("");
for (String w : words) {
k.set(w+"\001"+random.nextInt(numReduceTasks));
v.set(1);
context.write(k, v);
}
}
}
第2步map阶段把key拼接de随机字符串去掉,再聚合:
public static class SkewWordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
Text k = new Text();
IntWritable v = new IntWritable();
@Override
protected void map(LongWritable key, Text value, Context context) throws java.io.IOException ,InterruptedException {
String[] words = value.toString().split("\t");
v.set(Integer.parseInt(words[1]));
String[] split = words[0].split("\001");
k.set(split[0]);
context.write(k, v);
}
}
11.控制输入输出格式
// job.setInputFormatClass(TextInputFormat.class); 默认的输入组件
job.setInputFormatClass(SequenceFileInputFormat.class);
// job.setOutputFormatClass(TextOutputFormat.class); // 这是默认的输出组件
job.setOutputFormatClass(SequenceFileOutputFormat.class);
12.可以使用内部类一次性实现,Mapper,Reducer, Main(JobSubmitter).复杂逻辑可以通过分步MapReduce实现.
public class FriendsTwo {
public static class FriendsTwoMapper extends
Mapper<LongWritable, Text, Text, Text> {
Text k = new Text();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
k.set(split[0]);
v.set(split[1]);
context.write(k, v);
}
}
public static class FriendsTwoReducer extends
Reducer<Text, Text, Text, Text> {
@Override
protected void reduce(Text key, Iterable<Text> users, Context context)
throws IOException, InterruptedException {
ArrayList<String> userList = new ArrayList<>();
for (Text user : users) {
userList.add(user.toString());
}
Collections.sort(userList);
StringBuffer sb = new StringBuffer();
for (String user : userList) {
sb.append(user).append(" ");
}
context.write(key, new Text(sb.toString()));
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(FriendsTwo.class);
job.setMapperClass(FriendsTwoMapper.class);
job.setReducerClass(FriendsTwoReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//以上一步输出路径为输入路径
FileInputFormat.setInputPaths(job, new Path(
"e:\\wordcount\\output\\friends"));
FileOutputFormat.setOutputPath(job, new Path(
"e:\\wordcount\\output\\friends1"));
job.waitForCompletion(true);
}
}
Hadoop-MapReduce核心原理总结:
mapreduce程序在YARN上启动-运行-注销的全流程: