MapReduce的定义
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
MapReduce的核心功能是将用户编写的业务逻辑代码和自带默认组件结合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
MapReduce的优缺点
优点:
MapReduce易于编程,他简单的实现一些接口,就可以完成一个分布式程序。这个分布式程序可以分布到大量廉价的PC机器上的运行;也就是说,编写一个分布式程序,和写一个串行的程序是一样的,这样子可以减轻了程序员的负担,从而使Hadoop变成变得这么流行。
他有良好的扩展性,当计算资源不可以满足当前的需求的时候,就可以通过增加机器的数量来扩展集群的计算能力。
高容错性,既然可以在廉价的机器上运行,所以在逻辑层次上就要求它具有很高的容错性。比如,其中一个机器挂了,他就可以将原本在它身上的计算任务交给其他机器来运行,不至于由于他的死亡导致整个集群的任务的失败。而且本过程是不需要人为干预的,而是由Hadoop内部完成的。
适合PB级以及以上的海量数据的离线处理,可以实现上前台服务器集群的并发工作,提供数据处理能力。
缺点:
不擅长实时的计算,无法在毫秒级返回结果,甚至在秒级都不一定可以返回结果。
不擅长流式计算,流式计算的输入数据是动态的,但是MaoReduce的输入数据是静态的,不可以动态变化,这是因为MapReduce自身的设计特点决定了数据源必须是静态的。
不擅长有向图的计算,如果多个图存在依赖关系,后一个应用程序的输入是前一个程序的输出,在这种情况下,MapReduce的工作具体步骤是将每一个程序的输出都要写到磁盘上,之后再进行下一个程序的时候再从磁盘中读数据,这样子做会造成大量的磁盘IO,导致性能的低下。
MapReduce的核心思想
假设有一个任务需要将两个文件中的单词进行分类,其中首字母为a~ p为一个文件,q~ z为一个文件。MapReduce分为两个阶段,Map阶段和Reduce阶段。具体在各个阶段的工作。
Map:
1.读数据,并按行处理
2.按空格切分行内的单词
3.KV键值对(单词,1)
4.将所有的KV键值对中的单词,按照单词的首字母,分成两个分区写入磁盘。
(由于两个文件在三个块中存储,那么就会有三个Map阶段)
Reduce:
1.MapReduce运行程序一般都需要分成两个阶段,Map阶段和Reduce阶段
2.Map阶段的各个Map任务都是完全并发的Map Task,完全并行运行,互不相干
3.Reduce阶段的并发Reduce Task,完全互不相干,但是它们一开与上一个阶段的全部的Map Task阶段的并发实例的输出
4.MapReduce编程模型只可以包含一个Map阶段和一个Reduce阶段,如果用户的业务要求逻辑十分复杂,那么就只可以是多个MapReduce程序串行运行了。虽说上图中写了三个MapTask,但是它们是一个Map阶段。上图中有两个ReduceTask,但是它们是一个Reduce阶段。
MapReduce进程
一个完整的MapReduce程序在分布式运行的时候都有三个实例进程
1.MRAppMaster:负责整个程序的过程调度和状态协调
2.MapTask:负责Map阶段的整个数据处理流程
3.ReduceTask:负责整个Reduce阶段的整个数据处理流程
官方的WordCount源码
源代码在哪儿
github地址
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.examples;
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class WordCount {
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length < 2) {
System.err.println("Usage: wordcount <in> [<in>...] <out>");
System.exit(2);
}
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
for (int i = 0; i < otherArgs.length - 1; ++i) {
FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
}
FileOutputFormat.setOutputPath(job,
new Path(otherArgs[otherArgs.length - 1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
可以看到有一些数据类型并不是Java语言中定义的类型,这实际上是Hadoop序列化的类型:
可以看出,除了String被序列化成Text,其余的都是将首字母大写之后在后面加上writeable就形成了Hadoop上的新的一种数据类型。这里的将首字母大写是因为,序列化是通过Java进行的序列化,所以它们也就应该遵守Java语言的规则类名的首字母大写操作。
MapReduce的编程规范
用户编写的程序都分成三个部分
1.Mapper
2.Reducer
3.Driver
这个格式是固定的,我们编写的程序都要遵守这个该格式。
Mapper阶段
1.用户定义的Mapper要继承自己的父类
2.Mapper的输入数据是KV键值对的形式,KV的类型可以自己定义。
3.Mapper中的业务逻辑写在map()方法中
4.Mapper的输入数据是KV键值对的形式,KV的类型可以自己定义
5.map()方法(MapTask进程)对每一个<K,V>调用一次。
Reducer阶段
1.用户定义的Reducer要继承自己的父类
2.Reducer的输入数据类型对应Mapper的输出数据类型,同样是KV键值对类型
3.Reducer的业务逻辑写在reducer()方法中
4.ReduceTask进程对每一组相同的K的对调用一次reduce()方法。
Dirver阶段
1.相当于yarn集群的客户端
2.用于提交整个程序的yarn集群
3.提交的是封装了MapReduce程序相关运行参数的Job对象。
自己写一个WordCount程序
需求分析
需求就是在给定的文本文件中统计每一个单词出现的总次数。
对于例子:有一个txt文件hello.txt文件:
root root
student
student
teacher teacher
one
two
three
hadoop
期望的输出如下:
我们根据已有数据和期望得到的数据进行分析,可以得到这样的数据结果:
环境的准备:
创建maven工程,填写该程序的依赖。创建log4j.properties以防止已知出现的警告。最后创建package就可以了。
创建WordCountMapper类:
创建Reducer类
创建Driver类
共七步,其中存在main方法
提前生成一个配置文件。让job获取
1.获取job对象
2.设置jar包的存储位置
通过一个类文件去设置jar包存储的位置
3.关联之前写的map和reduce类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
4.设置Mapper阶段的输出数据类型(K,V类型)
job.setMapOutputKeyClass(Text.class)
job.setMapOutputValueClass(IntWritable.class)
5.是指最终输出数据类型也就是Reducer
job.setReduceOutputKeyClass(Text.class)
job.setReduceOutputValueClass(IntWritable.class)
6.设置输入路径和输出路径
FileInputFormat.setInputPaths(job, Path(args[0]));设置输入路径为第一个参数
FileInputFormat.setOutputPaths(job, Path(args[0]));设置输出路径为第二个参数
7.提交job
job.witForCompletion(true);
wordcount案例测试
在集群上运行案例测试的时候
在添加一些依赖之后可以生成jar包
打包之后会生成两个两个包,一个小包(不加依赖),一个大包(加依赖),(大和小是根据它们所占用的空间的大小进行区分的。)
之后将jar包拷贝到Hadoop下,就可以执行了,共有三个参数,第一个是主类,第二个是输入,第三个是输出。对于瘦包来说可以直接运行成功,但是对于胖包来说(加依赖的包),是不可以执行成功的,因为他认为第一个主类的参数当作了输入路径,把输入路径当作了输出路径。但是可以通过将pom.xml中的某一个配置删除之后就可以了。
序列化概念
什么是序列化:序列化就是将内存中的对象,转化成字节序列(或者其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。
对于网络传输来说也是先将内存中的内容序列化成磁盘中的数据之后再进行数据的传输。
反序列化就是将接收到的字节序列(或者其他的数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。所以,对于同一个对象存储在磁盘和内存中的存在形式是不一样的。
为什么要序列化呢
一般来说,活得对象只可以生存在内存之中,关机断电之后内存中就没有课活得对象了。
而且获得对象只可以由本地进程的使用,不可以通过网络发送到另一台主机。
然而,序列化可以进行存储活得对象,可以将活得对象发送到远程的计算机(通过网络)。
为什么不直接使用JAVA的序列化,Hadoop还要重新定义一种序列化呢
1.JAVA的序列化是一个重量级的序列化框架。
2.一个对象被序列化之后就会附带很多的额外信息
3.不便于在网络中的高效的传输
4.由于JAVA序列化以上的特点,导致java的序列化再Hadoop中不受欢迎。所以,hadoop自己开发了一套序列化机制
Hadoop的序列化的特点:
1.紧凑:高效的使用存储空间
2.快速:读写数据的开销较小
3.可扩展:通信协议的升级,可升级
4.互操作性:支持多语言的交互
自定义bean对象实现序列化接口
为什么要自定义呢
因为在常用的序列化类型是不可能完全满足所有的需求的
以下是已有的序列化类型:
比如在Hadoop内部要传递一个bean对象,那么就要去实现序列化接口。
实现的步骤:
1.必须实现Writable接口
2.反序列化的时候,需要调用空参的构造函数,所以必须有空参构造函数:
public FlowBean(){
super();
}
3.重写序列化方法
4.重写反序列化方法
5.序列化的舒徐必须要与反序列化的顺序完全一致,因为是一个先进先出的队列。
6.要想把结果显示在文件中,需要重写toString(),可以用“\t”分隔开,以方便后续的调用
(正常的情况下,以上六步即可,但是hadoop的序列化还要实现第七步)
7.如果需要将自定义的bean放在key中进行传输,那么还需要实现Comparable接口(实现接口之后就可以在shuffle过程中进行排序了),MapReduce框架中的Shuffle过程要求key必须能排序。
切片与MapTask并行度决定机制
1.问题引出
MapTask的并行度决定Map阶段的任务处理的并发度,进而影响整个Job的处理速度。
但是并不是说MapTask的数量越多越好,比如1G的数据,启动8个MapTask可以提高集群的并发处理能力,但是对于1K的数据,并不会提高性能。
2.MapTask并行度决定机制
数据块:Block是HDFS物理上把数据分成一块一块。
数据切片:数据切片只是在逻辑上对输入进行切分,并不会在磁盘上将其切分成片进行存储。
以上就是数据块和数据切片之间的不同点。
如果切片的大小和数据块的大小不成比例的话,可能会导致DN1上的数据需要在DN2上去执行。
1.一个Job的Map阶段并行度有客户端在提交Job的切片数决定
2.每一个Spilt切片分配一个MapTask并行实例处理
3.默认情况下,切片大小=BlockSize
4.切片时不考虑数据集整体,而是逐个对每一个文件进行切片
FileInputFormat切片过程
1.先找到数据的存储路径
2.开始遍历处理(规划切片)目录下的每一个文件
3.遍历第一个文件:比如说big.dat
a.获取文件大小fs.sizeOf(big.dat)
b.计算切片的大小computeSpiltSize(Math.max(minSize, Math.max(maxSize, blockSize)))=blockSize=128M
这里的minSize时最小数据的大小,默认是1M,maxSize是最大大小,默认是一个天文数字,所以这里最后的取值应该是BlockSize。
c.默认情况下,切片大小就是blockSize
d.之后开始切分,每一次切片的时候,都要进行判断剩下的部分是否大于切片大小的1.1倍,如果不大于就不对其进行切分。
e.将切片信息写到切片规划文件中
f.切片的核心过程在getSpilts()方法中完成
gInputSpilt只是记录了切片的元数据信息,比如说各个切片的起始位置,长度以及所在的节点列表等。
4.提交切片规划文件到Yarn上,Yarn上的MRAppMaster就可以根据切片规划文件计算开启的MapTask的数量。
CombineTextInputFormat切片机制
框架默认的TextInputFormat切片机制是对任务按照文件规划进行的切分,也就是说无论文件的大小,都会是一个单独的切片,都会交给一个MapTask,所以说,如果这样子来的话,就会产生大量的MapTask,从而导致效率极低。
CombineTextInputFormat适用于小文件过多的长江,可以将多个小文件从逻辑规划到一个切片中(是逻辑上,物理上的存储方式并不会发生转变,只是从逻辑上把多个小文件交给一个MapTask处理)。
虚拟存储切片的最大值的设置
CombineTextInputFormat.setMaxInputSpiltSize(job,4194304)4MB,以字节为单位的。注意虚拟存储切片的最大值的设置最好是根据小文件的大小情况来具体设置具体的值。
例子:
如果把CombineTextInputFormat.setMaxInputSpiltSize设置为4MB
如果是一个10MB的小文件,就会先切出来一个4MB,剩下的6MB再在两个块中评分。
切片的过程,如果虚拟存储的文件大小大于4MB的话,就自己形成一个切片,否则就跟之后的文件进行合并,当合并到刚好大于或者等于的时候形成新的一个切片。
FileInputFormat实现类
首先FileInputFormat是一个接口,在运行MapReduce程序的时候,输入文件的文件格式包括了基于行的日志文件,二进制格式文件,数据库表等,那么根据这些个不同的数据类型,MapReduce如何读取这些数据。
其中FileInputFormat接口中已经实现的类包括
TextInputFormat
可以按行读取数据,通过这种方法读取的进来的是一个键值对。
键:存储改行在整个文件中的其实字节的偏移量,LongWritable类型的
值:这行的数据的内容,不包括任何的终止符(换行,回车),是Text类型的。
KeyValueInputFormat
每一行均为一条记录,被分割符分割为key,value
可以在驱动类中设置conf.set(KeyValueLineRecord.KEY_VALUE_SEPERATOR,"\t");分隔符,默认的分割符是tab键
NLineInputFormat
如果使用NLineInputFormat,对于每一个map进程处理的InputSpilt不再按照Block块去进行划分,而是按照NLineInputFormat枝顶的行数N来进行划分。
就是输入文件的总行数/N等于切片的个数
如果不可以对其进行完全的整除,就会将其余数部分生成一个新的切片。
自定义InputFormat
hadoop自带的不可以满足所有的场景,所以我们还需要自定义InputFormat用来解决实际问题。
自定义一个类,机成FileInputFormat类。
改写RecordReader,实现一次读取一个完整文件封装为KV。
在输出时使用的时sequenceFileOutputFormat输出合并文件的。
那么,到此为止,学过的处理小文件的方法就有了三种:
HAR文件
CombineTextInputFormat
自定义InputFormat处理小文件
MapReduce工作流程
第二步其实就是对任务做切片
接下来提交的切片信息,本地运行中是不用提交jar包的,因为不需要获取输入的路径
之后Yarn就可以计算出MapTask的个数
第七步做的是向左边写元数据(右边的Key,Value的起始位置,终止位置,分区位置等),右边写真实的输出数据Records。分区的目的是主要是为了在reduce步骤说明,有几个分区就会Reduce出来几个文件。
存入环形缓冲区之后呢,就会先对去进行分区,之后在每一个分区内进行排序。之后一些到磁盘上,因为内存中的可存储数据的容量还是有限的。
之后将溢出到磁盘上的内容和当前的内存上的内容按照分区的不同进行归并排序。
有多少个数据分区,就会对应的启动多少个ReduceTask
第14步中的所谓的一次读取一组的意思就是一次性读取一组Key相同的数据,之后将他们各自的Value值进行相加求和。
最后ReduceTask1和ReduceTask2各自输出各自的结果文件。那么也就是说该操作共生成了两个文件,也就是两个分区生成了两个文件,与之前的说的分区数与Reduce个数相同,与最后的结果生成的文件相同。
shuffle机制
Map方法之后,Reduce方法之前的数据处理过程,称之为Shuffle,Shuffle涉及到了分区,排序,Combiner合并,归并排序,数据压缩等技术。
partition分区
问题的引出,要求将统计结果按照不同的条件输出到文件当中去(分区)。比如:将统计结果按照手机归属地,不同省份输出到不同的文件中去。
那么需要进行分区,具体操作何如呢:
默认的Partition分区如下
默认的分区其实是根据Key的hashCode对ReduceTask个数取模得到的,用户根本无法控制哪个Key存储到哪个分区中。
numReduceTasks就是最后输出的文件的个数。
就是hash值对分区的个数取余得到的
自定义partition分区
如何让用户自己定义分区呢?
首先自定义分区类:该类继承Partitioner,并且在类内重写方法getPartition()方法
Partitioner的输入类型与Map输出的类型相同的。
在job驱动类中,设置自定义Partitioner
job.setPartitionClass(CustomPartitioner.class);
自定义Partitioner类之后,眼根据自定义的Partitioner的逻辑设置相应数量的ReducerTask
job.setNumReduceTasks(5);
设置5个ReduceTask。
排序
排序是MapReduce框架中最重要的操作之一。
MapTask和ReducerTask均会按照Key进行排序。该操作属于Hadoop的默认行为。在任何的应用中的数据都会被排序,而不管他是不是在逻辑上需要的。所以在自定义bean序列化的时候是需要我们将其进行排序的。
默认的排序是按照字典的顺序排序,实现该排序的方法就是快速排序方法。当然可以自定义排序的方法。
MapTask的排序:
它会将处理的结果暂时存储到环形缓冲区中,当环形缓冲区的使用率达到了80%之后就会对缓冲区中的数据进行排序,并将这些排序之后的书籍写到磁盘上,之后当所有的输出的数据被处理完毕之后就会被所有的文件进行归并排序。
对于ReduceTask:
它从每一个MapTask上远程拷贝相应的数据文件,如果磁盘上的文件数目达到了一定的阈值,那么就会将其写到磁盘上,否则存储在内存中,如果磁盘上的文件数目达到了一定的阈值,则进行一次归并排序用来生成一个更大的文件;如果内存中超过了阈值,则进行一次合并之后将数据一些到磁盘上麦当所有的数据拷贝完成之后,在ReduceTask就会对在内存上的数据和之前的在磁盘上存储的数据进行一次归并排序,以完成ReduceTask的数据的排序。
排序的分类
1.部分排序:
MapReduce根据输入记录的键对数据进行排序,以保证每一个文件的数据都是有序的
2.全排序:
最终输出的结果只有一个文件,而且文件的内部是有序的,实现方法只设置一个ReducerTask就可以实现全排序了。但是笨方法在处理大型文件的时候效率低,因为这种情况写一个ReducTask和多个ReducerTasks实现的效率是无法比较的。如果大型文件只需要一个输出文件的时候,可以实行多级的Job串联,将本级的Job的输出将下一个Job的输入,可以提升该类工作的效率。
3.辅助排序:
应用于在Reduce端对Key进行分组的时候,要将Key是一个Bean对象的时候的只选择其中的若干个属性进行排序的方法。
4.二次排序:
在自定义条件中,如果比较了两次就是二层排序,如果比较了 三次,就是三层排序。
5.自定义排序:
原理分析,在bean对象作为Key传输的时候需要实现EritableComparable接口,之后重写comparableTo()方法,就可以实现用户想实现的正序排序或者是倒序排序。
Combiner合并
1.Combiner是MR程序中Mapper和Reducer之外的一种组件
2.Cimbiner组件的父类是Reducer
3.Combiner和Reducer的区别在于运行的位置:
Combiner运行在每一个MapTask节点运行,有几个MapTask就有几个Combiner。作用是做一个局部的汇总,以减少网络上的传输。
Reducer是接受全局的所有的MapTask的输出结果。只有一个
4.combiner的能够应用的前提是不可以影响最后的业务的逻辑,并且combiner的输出必须是Mapper的输出。
自定义一个combiner的实现步骤
1.自定义一个combiner,继承reducer,并且要重写reduce()方法。
2.在Job类中的设置
job.setCombinerClass(WordcountCombiner.class);
应该注意的是,在所有的Combiner的自定义中,必须要在Job中去设置使用的是哪一个combiner,否则将会去使用默认的,或者是不适用combiner,如果这样的话,我们自定义的combiner就毫无意义了。
辅助排序(分组排序)
在Reducer阶段根据数据的某一个或者几个字段进行分组
对于分组排序的步骤
1.自定义类,继承WritableComparator
2.重写compare()方法,方法参数传入两个对象之后再内部实现具体的比较的业务逻辑
3.创建一个构造将比较对象的类传递给父类。
Shuffle机制
在Mapper结束之后在Reducee开始之前的进行的步骤,都与Shuffle机制密不可分
步骤顺序是:分区,排序,Combiner合并
自定义OutputFormat
OutputFormat是MapReduce输出的基类,所有实现MapReduce输出的类都实现了OutputFormat接口,下面接受几种常见的OutputFormat实现类
1.文本输出TextOutputFormat
默认的输出的格式就是TextOutputFormat,它把每一条记录都写为文本行。他的键和值可以使任意的类型,因为TextOutputFormat条用toString()方法可以将他们转化为字符串。
2.SequenceFileOutputFormat
键是文件的名称,值是文件的内容。将SequenceFileOutputFormat输出作为后续的MapReduce任务的输入,这就是一种很好的输出格式,因为他的格式紧凑,所以很容易被压缩。
3.自定义OutputFormat
根据用户的需求,用户可以进行自定义他们的输出。可以将输出直接存储到数据库或者HDFS上,如果不自定义的话,可能还需要使用shell命令或者JAVA API操作去实现向数据库中或者HDFS上的上传操作。
Hadoop数据压缩
为什么要进行数据的压缩呢
压缩技术可以有效减少底层存储系统HDFS读写字节数,以加快速度
压缩提高了网络贷款和磁盘空间的效率
在运行MR程序的时候I/O操作,网络数据传输,Shuffle和Merge要花费大量的时间,尤其是在数据规模大,而且工作负载密集的情况下
因此使用数据压缩对MR的效率的提升就显得尤为重要了。
鉴于磁盘I/O和网络带宽是hadoop的宝贵的资源,数据压缩对于节省资源,最小化磁盘I/O和网络的传输非常有帮助,因为他们可以减小数据的所占空间,就可以减少I/O和网络传输的根本上的传输量的大小。
可以在任意的MapReduce阶段启用压缩
不过尽管压缩和解压的操作的CPU开销并不高,但是其性能的提升和资源的节约并不是说没有代价的。
压缩是提供Hadoop运行效率的一种的优化的策略。通过对Mapper,Reducer运行过程的数据进行压缩以减小磁盘的I/O,提高MR的运行速度
注意:采用压缩技术虽然减少了I/O,但是同样也增加了CPU的运算的负担,所以,压缩特性运用得当可以提高性能,但是运用不当也同样可以减低性能。
所以说使用压缩的基本原则如下:
1.运算密集型的Job,少用压缩
2.I/O密集型的Job,多用压缩
MR支持的压缩编码
总共有五中压缩编码,是MR所支持的
对于以上五中的压缩方式的选择
Gzip
压缩率比较高,而且压缩/解压缩速度快,Hadoop本身就只吃,在应用处理中处理Gzip的格式的文件和直接处理文本文件一样,而且大部分的Linux系统都自带Gzip命令
缺点就是,不支持Spilt
应用场景:当每一个文件被压缩之后在130MB之内的(一个块的大小的文件)都可以考虑去采用Gzip格式,(这么说的主要原因还是因为他不可以进行切片操作。)
比如,一天或者一个小时的日志文件被压缩成Gzip文件
Bzip2
支持Spilt;具有较高的压缩率,要比Gzip的压缩率高;hadoop自带,使用方便。
缺点就是压缩,解压速度慢
应用场景:适合对速度要求不高但是对压缩率要求高的时候,或者输出之后的数据需要存档以减少磁盘空间并且在之后很少使用该输出的时候。或者对单个很大的文本文件想压缩以减少它的存储空间,同时有需要支持Spilt,而且兼容之前的应用程序的情况。
Lzo
压缩解压速度快,合理的压缩率;支持Spilt,是Hadoop最流行的压缩格式,可以在Linux系统上安装lzop命令,使用方便
缺点是压缩率要比Gzip低,Hadoop本身不支持,需要安装;在应用中对Lzo格式的文件做了特殊的处理(为了支持Spilt需要建立索引,还需要制定InputFormat为Lzo格式)。
应用场景:在一个很大的文本文件,压缩之后还大于200MB以上的可以考利,而且单个文件越大,Lzo的优势就越明显
Snappy
高压缩速率和合理的压缩率
缺点是不支持Spilt;压缩率比Gzip低;Hadoop本身不支持,需要安装
应用场景:当MapReduce作业的Map输出数据比较大的时候,作为Map和Reduce的中间数据的压缩格式;或者作为一个MapReduce作业的输出和另一个MapReduce作业的输入的时候。
压缩位置的选择
压缩参数的设置
Yarn基本架构
什么是Yarn,Yarn是一个资源调度的平台,负责为运算程序提供服务器运算资源,相当于是一个分布式的操作系统平台,MapReduce等运算程序则相当于操作系统之上的应用程序
ResourceManager,NodeManager,ApplicationMaster,Container组成
1.resourcemanager的主要作用
处理客户端的请求
监控nodemanager状态
启动或监控ApplicationMaster
资源的分配和调度
2.nodemanager的主要作用
管理单个节点的资源
处理来自resourcemanager的命令
处理来自ApplicationMaster的命令
3.ApplicationMaster的主要作用
负责数据的切分
为应用程序申请资源并分配任务
任务的监控和容错
4.Container
是Yarn中的资源抽象,它封装了某一个节点上的多维度资源,比如内存,CPU,硬盘,网络等资源。
Yarn的工作机制
1.申请Application
2.yarn给出资源提交路径和application_id,让job向该路径提交资源
3.提交所需资源,上传切片信息,配置,jar包等
4.提交完毕之后就可以申请运行了
5.不是说有提交之后就立马会运行,未必会有空余的资源,所以打包之后放在队列里
6.等到了空闲的nodemanager之后,向nodemanager分发该任务
7.nodemanager创建container,装的是各种资源
8.nodemanager下载资源到本地,下载的路径是领取任务的时候就知道了
9.根据切片信息向Resourcemanager申请MapTask容器
10.领取任务,创建容器(要有jar包)
11.ApplicarionMaster向运行的nodemanager发送程序运行脚本,告诉他们具体如何运行
12.申请ReducerTask
13.申请成功之后将Map的结果拷贝到ReduceTask节点上,让他可以运行。
14.运行结束之后,会注销自己
资源调度器
1.FIFO先进先出
2.Capacity Scheduler(容量调度器)
3.Fair Scheduler(公平调度器)
Hadoop2.7.2默认的资源调度器是Capacity Scheduler(容量调度器)
HA高可用
所谓的HA就是高可用
什么是高可用呢,就是7*24小时不中断服务实现干可用的最关键策略就是消除单点故障。HA严格来说应该分成各个组件的HA机制HDFS的HA和Yarn的HA
在Hadoop2.0之前,在HDFS中存在单点故障问题SPOF
Namenode主要在一下两个方面影响HDFS集群
1.namenode机器发生意外导致集群无法使用,知道管理员重启之后才可以继续提供服务
2.namenode机器需要升级,包括软件的升级和硬件的升级,在该过程中,集群童颜无法正常使用
HDFS HA功能通过实现配置Active/Standby两个namenode实现集群中的对namenode热备来解决上述的问题,如果出现了故障,可以通过这种方式将namenode很快滴切换到另一台机器上。
手动解除故障
原理,在实际生活中基本不可能手动去解除故障
有两个namenode,一个是active,一个是standby,两个节点的元数据共享,通过JN进行共享,实际上JN中存储的就是fsimage和Edits。用一个监控脚本进行监控active的工作状态。监控到节点无法工作之后,就会想系统管理员通知,之后认为的无冲洗启动机器,是他可以正常的进行工作。但是要注意是不可以将监控脚本放在standby上,该节点监控到active发生故障之后就自己自动的去替代active进行工作。这么做是万万不可以的,这样子做会出现脑裂的情况。当两个节点不可以通信的时候,可能会误认为active无法工作,之后私自将自己的状态改为active会出现两个首脑,就是说出现了脑裂。这种情况下对于分布式系统来说很危险。
所以,内部有一个屏蔽脑裂的机制,当standby想要成为active的时候必须向原来的active节点申请,申请成功之后才可以将自己的状态改为active状态的。当active原地爆炸的时候,无论如何都无法启动的时候,该集群就直接报废了。
自动解除故障
注意,当ZKFC检测到发生了假死的时候,会通知Standby节点,之后该节点不管你是假死还是真死,都会直接执行一个命令,将你杀死。防止了脑裂。
Yarn的HA
在Yarn的高可用中没有ZKFC用来监控ResourceManager的健康状态。直接与zookeeper相连的
配置完成之后可以再rm1上启动yarn服务:
start-yarn.sh
之后再rm2上也启动resourcemanager进程
yarn-deamon.sh start resourcemanager
就可以保证yarn的HA了