04大数据技术之Hadoop(MapReduce)(老师:尚硅谷)
第1章 MapReduce概述
1.1 MapReduce定义
MapReduce 是一个分布式运算程序的编程框架,是用户开发“基于 Hadoop 的数据分析应用”的核心框架。
MapReduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个 Hadoop 集群上。
1.2 MapReduce优缺点
1.2.1 优点
1.2.2 缺点
-
不擅长实时计算 MapReduce 无法像 MySQL 一样,在毫秒或者秒级内返回结果。 -
不擅长流式计算 流式计算的输入数据是动态的,而 MapReduce 的输入数据集是静态的,不能动态变化。 -
不擅长DAG(有向五环图)计算 每个 MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘 IO,导致性能非常的低下。
1.3 MapReduce核心思想
- 分布式的运算程序往往需要分成至少 2 个阶段。
- 第一个阶段的 MapTask 并发实例,完全并行运行,互不相干。
- 第二个阶段的 ReduceTask 并发实例互不相干,但是他们的数据依赖于上一个阶段的所有 MapTask 并发实例的输出。
- MapReduce 编程模型只能包含一个 Map 阶段和一个 Reduce 阶段,如果用户的业务逻辑非常复杂,那就只能多个 MapReduce 程序,串行运行。
1.4 MapReduce进程
一个完整的 MapReduce 程序在分布式运行时有三类实例进程:
- MrAppMaster:负责整个程序的过程调度及状态协调。
- MapTask:负责 Map 阶段的整个数据处理流程。
- ReduceTask:负责 Reduce 阶段的整个数据处理流程。
1.5 官方WordCount源码
采用反编译工具反编译源码,发现 WordCount 案例有 Map 类、Reduce 类和驱动类。且数据的类型是 Hadoop 自身封装的序列化类型。
1.6 常用数据序列化类型
Java类型 | Hadoop Writable类型 |
---|
Boolean | BooleanWritable | Byte | ByteWritable | Int | IntWritable | Float | FloatWritable | Long | LongWritable | Double | DoubleWritable | String | Text | Map | MapWritable | Array | ArrayWritable | Null | NullWritable |
1.7 MapReduce编程规范
用户编写的程序分成三个部分:Mapper、Reducer 和 Driver。
-
Mapper阶段
- 用户自定义的Mapper要继承自己的父类
- Mapper的输入数据是KV对的形式(KV的类型可自定义)
- Mapper中的业务逻辑写在map()方法中
- Mapper的输出数据是KV对的形式(KV的类型可自定义)
- map()方法(MapTask进程)对每一个调用一次
-
Reducer阶段
- 用户自定义的Reducer要继承自己的父类
- Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
- Reducer的业务逻辑写在reduce()方法中
- ReduceTask进程对每一组相同k的组调用一次reduce()方法
-
Driver阶段 相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。
1.8 WordCount案例实操
1.8.1 本地测试
【需求】在给定的文本文件中统计输出每一个单词出现的总次数
-
环境准备 创建Maven工程,MapReduceDemo pom.xml添加依赖 <dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
在项目的 src/main/resources 目录下,新建一个文件,命名为“log4j.properties”,在文件中填入。 log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
创建包名:com.atguigu.mapreduce.wordcount   WordCountMapper类
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
Text k = new Text();
IntWritable v = new IntWritable(1);
@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){
k.set(word);
context.write(k,v);
}
}
}
WordCountReducer类
public class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
int sum;
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
sum = 0;
for (IntWritable count : values){
sum += count.get();
}
v.set(sum);
context.write(key,v);
}
}
WordCountDriver类
public class WordCountDriver {
public static void main(String[] args) throws IOException,ClassNotFoundException,InterruptedException {
//1. 获取配置信息以及获取job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2. 关联本Driver程序的jar
job.setJarByClass(WordCountDriver.class);
// 3. 关联mapper输出的kv类型
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4. 设置mapper输出的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5. 设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6. 设置输入和输出路径
FileInputFormat.setInputPaths(job,new Path("D:\\input\\inputword"));
FileOutputFormat.setOutputPath(job,new Path("D:\\hadoop\\output1"));
// 7. 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
【注意:输出目录不能创建完全,否则报错】
1.8.2 提交到集群测试
-
添加打包插件 -
将程序打成jar包  -
这里可以更改一下输入输出路径为命令行参数 -
传至虚拟机  -
运行 查看集群上已有数据  这里记得先启动Hadoop103的start-yarn.sh  执行完毕 
第2章 Hadoop序列化
2.1 序列化概述
-
什么是序列化 序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。 反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。 -
为什么要序列化 序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。 -
为什么不用Java的序列化 Java 的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。 -
Hadoop序列化特点
- 紧凑:高效实用存储空间
- 快速:读写数据的额外开销小
- 互操作:支持多语言的交互
2.2 自定义bean对象实现序列化接口(Writable)
具体实现步骤:
- 必须实现Writable接口
- 反序列化时,需要反射调用空参构造函数,所以必须有无参构造
- 重写序列化方法
- 重写反序列化方法
- 注意反序列化的顺序和序列化的顺序完全一致
- 要想把结果显示在文件中,需要重写 toString(),可用"\t"分开,方便后续用。
- 如果需要将自定义的 bean 放在 key 中传输,则还需要实现 Comparable 接口,因为MapReduce 框中的 Shuffle 过程要求对 key 必须能排序。
2.3 序列化案例实操
FlowBean.java
public class FlowBean implements Writable {
private long upFlow;
private long downFlow;
private long sumFlow;
public FlowBean(){
}
public long getUpFlow() {
return upFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public long getDownFlow() {
return downFlow;
}
public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setSumFlow() {
this.sumFlow = this.upFlow + this.downFlow;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.upFlow = dataInput.readLong();
this.downFlow = dataInput.readLong();
this.sumFlow = dataInput.readLong();
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
}
FlowMapper.java
public class FlowMapper extends Mapper<LongWritable, Text,Text,FlowBean> {
private Text outK = new Text();
private FlowBean outV = new FlowBean();
@Override
protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split("\t");
String phone = split[1];
String up = split[split.length - 3];
String down = split[split.length - 2];
outK.set(phone);
outV.setUpFlow(Long.parseLong(up));
outV.setDownFlow(Long.parseLong(down));
outV.setSumFlow();
context.write(outK,outV);
}
}
FlowReducer.java
public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
private FlowBean outV = new FlowBean();
@Override
protected void reduce(Text key,Iterable<FlowBean> values,Context context) throws IOException,InterruptedException {
long totalUp = 0;
long totalDown = 0;
for (FlowBean flowBean : values){
totalUp += flowBean.getUpFlow();
totalDown += flowBean.getDownFlow();
}
outV.setUpFlow(totalUp);
outV.setDownFlow(totalDown);
outV.setSumFlow();
context.write(key,outV);
}
}
FlowDriver.java
public class FlowDriver {
public static void main(String[] args) throws IOException,ClassNotFoundException,InterruptedException {
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
job.setJarByClass(FlowDriver.class);
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
job.setOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
FileInputFormat.setInputPaths(job,new Path("D:\\inputflow"));
FileOutputFormat.setOutputPath(job,new Path("D:\\flowoutput\\output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}

第3章 MapReduce框架原理
3.1 InputFormat数据输入
3.1.1 切片与MapTask并行度决定机制
**数据块:**Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位。
**数据切片:**数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。数据切片是MapReduce程序计算输入数据的单位,一个切片会对应启动一个MapTask。
- 一个Job的Map阶段并行度由客户端在提交Job时的切片数决定
- 每一个Split切片分配一个MapTask并行实例处理
- 默认情况下,切片大小=BlockSize
- 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
3.1.2 Job提交流程源码和切片源码详解
Job提交流程源码详解
waitForCompletion()
submit();
connect();
new Cluster(getConfiguration());
initialize(jobTrackAddr, conf);
submitter.submitJobInternal(Job.this, cluster)
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
JobID jobId = submitClient.getNewJobID();
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
writeConf(conf, submitJobFile);
conf.writeXml(out);
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
FileInputFormat切片源码解析
- 程序先找到数据存储的目录
- 开始遍历处理(规划切片)目录下的每一个文件
- 遍历第一个文件ss.txt
- 获取文件大小fs.sizeOf(ss.txt)
- 计算切片大小
- 默认情况下,切片大小=blocksize
- 开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M
- 将切片信息写到一个切片规划文件中
- 整个切片的核心过程在getSplit()方法中完成
- InputSplit只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等
- 提交切片规划文件到YARN上,YARN上的MrAppMaster就可以根据切片规划文件计算开启MapTask个数。
3.1.3 FileInputFormat 切片机制
- FileInputFormat 切片机制
- 简单地按照文件的内容长度进行切片
- 切片大小,默认等于Block大小
- 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
- FileInputFormat切片大小的参数配置
- 源码中计算切片大小的公式
- 切片大小设置
- 获取切片信息API
3.1.4 TextInputFormat
FileInputFormat 实现类
FileInputFormat 常见的接口实现类包括:TextInputFormat、KeyValueTextInputFormat、 NLineInputFormat、CombineTextInputFormat 和自定义 InputFormat 等。
TextInputFormat
TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键是存储该行在整个文件中起始字节偏移量,LongWritable类型。值是这行的内容,不包括任何行终止符(换行符和回车符),Text类型。
3.1.5 CombineTextInputFormat 切片机制
框架默认的 TextInputFormat 切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个 MapTask,这样如果有大量小文件,就会产生大量的MapTask,处理效率极其低下。
应用机制:
CombineTextInputFormat 用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个 MapTask 处理。
虚拟存储切片最大值设置:
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m 注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。
切片机制:
生成切片过程:虚拟存储过程 + 切片过程
3.2 MapReduce工作流程
具体shuffle过程
- MapTask收集map()方法输出的kv对,放到内存缓冲区中
- 从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
- 多个溢出文件会被合并成大的溢出文件
- 在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序
- ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据
- ReduceTask会抓取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)
- 合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)
3.3 Shuffle机制
3.3.1 Shuffle机制
Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle。
3.3.2 Partition分区
默 认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区。
→ 自定义Partitioner
步骤
- 自定义类继承Partitioner,重写getPartition()方法
- 在Job驱动中,设置自定义Partitioner
- 自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask
【分区总结】
- 如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;
- 如果 1 < ReduceTask的数量 < getPartition的结果数,则有一部分分区数据无处存放,会异常
- 如 果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个
ReduceTask,最终也就只会产生一个结果文件 part-r-00000; - 分区号必须从零开始,逐一累加
3.3.4 WritableComparable排序
排序是MapReduce框架中最重要的操作之一。
MapTask和ReduceTask均会对数据按 照key进行排序。该操作属于Hadoop的默认行为。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。
- 对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
- 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。
【排序分类】
-
部分排序 MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序。 -
全排序 最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。 -
辅助排序 在Reduce端对key进行分组。 -
二次排序 在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。
【自定义排序WritableComparable原理分析】
bean 对象做为 key 传输,需要实现 WritableComparable 接口重写 compareTo 方法,就可以实现排序。
3.3.7 Combiner合并
- Combiner是MR程序中Mapper和Reducer之外的一种组件。
- Combiner组件的父类就是Reducer。
- Combiner和Reducer的区别在于运行的位置
- Combiner是在每一个MapTask所在的节点运行;
- Reducer是接收全局所有Mapper的输出结果;
- Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。
- Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。
【自定义Combiner实现步骤】
-
自定义一个 Combiner 继承 Reducer,重写 Reduce 方法 -
在 Job 驱动类中设置: job.setCombinerClass(WordCountCombiner.class);
3.4 OutputFormat 数据输出
3.4.1 OutputFormat接口实现类
OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了 OutputFormat接口。
常见的OutputFormat实现类
- OutoutFormat实现类
- 默认输出格式TextOutputFormat
- 自定义OutputFormat
3.5 MapReduce内核源码解析
3.5.1 MapTask 工作机制
- Read 阶段:MapTask 通过 InputFormat 获得的 RecordReader,从输入 InputSplit 中解析出一个个 key/value。
- Map 阶段:该节点主要是将解析出的 key/value 交给用户编写 map()函数处理,并产生一系列新的 key/value。
- Collect 收集阶段:在用户编写 map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的 key/value 分区(调用Partitioner),并写入一个环形内存缓冲区中。
- Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
- Merge 阶段:当所有数据处理完成后,MapTask 对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
3.5.2 ReduceTask 工作机制
- Copy 阶段:ReduceTask 从各个 MapTask 上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
- Sort 阶段:在远程拷贝数据的同时,ReduceTask 启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
- Reduce 阶段:reduce()函数将计算结果写到 HDFS 上。
3.5.3 ReduceTask 并行度决定机制
?MapTask 并行度由切片个数决定,切片个数由输入文件和切片规则决定。
ReduceTask 的并行度同样影响整个 Job 的执行并发度和执行效率,但与 MapTask 的并发数由切片数决定不同,ReduceTask 数量的决定是可以直接手动设置。
【注意事项】
- ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致。
- ReduceTask默认值就是1,所以输出文件个数为一个。
- 如果数据分布不均匀,就有可能在Reduce阶段产生数据倾斜
- ReduceTask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个ReduceTask。
- 具体多少个ReduceTask,需要根据集群性能而定。
- 如果分区数不是1,但是ReduceTask为1,是否执行分区过程。答案是:不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNum个数是否大于1。不大于1肯定不执行。
3.6 Join应用
3.6.1 Reduce Join
**Map 端的主要工作:**为来自不同表或文件的 key/value 对,打标签以区别不同来源的记录。然后用连接字段作为 key,其余部分和新加的标志作为 value,最后进行输出。
**Reduce 端的主要工作:**在 Reduce 端以连接字段作为 key 的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在 Map 阶段已经打标志)分开,最后进行合并。
3.6.3 Map Join
Map Join 适用于一张表十分小、一张表很大的场景。
优点:在 Map 端缓存多张表,提前处理业务逻辑,这样增加 Map 端业务,减少 Reduce 端数据的压力,尽可能的减少数据倾斜。
3.7 数据清洗(ETL)
“ETL( Extract-Transform-Load),用来描述将数据从来源端经过抽取(Extract)、转换(Transform)、加载(Load)至目的端的过程。ETL 一词较常用在数据仓库,但其对象并不限于数据仓库。 在运行核心业务 MapReduce 程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行 Mapper 程序,不需要运行 Reduce 程序。
3.8 MapReduce开发总结
-
输入数据接口:InputFormat
- 默认使用的实现类是:TextInputFormat
- TextInputFormat 的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key,行内容作为 value 返回。
- CombineTextInputFormat 可以把多个小文件合并成一个切片处理,提高处理效率。
-
逻辑处理接口:Mapper 用户根据业务需求实现其中三个方法:map() setup() cleanup () -
Partitioner 分区
- 有默认实现 HashPartitioner,逻辑是根据 key 的哈希值和 numReduces 来返回一个分区号;key.hashCode()&Integer.MAXVALUE % numReduces
- 如果业务上有特别的需求,可以自定义分区。
-
Comparable 排序
- 当我们用自定义的对象作为 key 来输出时,就必须要实现 WritableComparable 接口,重写其中的 compareTo()方法。
- 部分排序:对最终输出的每一个文件进行内部排序。
- 全排序:对所有数据进行排序,通常只有一个 Reduce。
- 二次排序:排序的条件有两个。
-
Combiner 合并 Combiner 合并可以提高程序执行效率,减少 IO 传输。但是使用时必须不能影响原有的业务处理结果。 -
逻辑处理接口:Reducer 用户根据业务需求实现其中三个方法:reduce() setup() cleanup () -
输出数据接口:OutputFormat
- 默认实现类是 TextOutputFormat,功能逻辑是:将每一个 KV 对,向目标文本文件输出一行。
- 可以自定义 OutputFormat
第4章 Hadoop数据压缩
4.1 概述
- 压缩的优缺点
- 优点:以减少磁盘 IO、减少磁盘存储空间。
- 缺点:增加 CPU 开销。
- 压缩原则
- 运算密集型的 Job,少用压缩
- IO 密集型的 Job,多用压缩
4.2 MR支持的压缩编码
压缩算法对比
压缩格式 | Hadoop自带? | 算法 | 文件扩展名 | 是否可切片 | 换成压缩格式后,原来的程序是否需要修改 |
---|
DEFLATE | 是 | DEFLATE | .deflate | 否 | 不需要 | Gzip | 是 | DEFLATE | .gz | 否 | 不需要 | bzip2 | 是 | bzip2 | .bz2 | 是 | 不需要 | LZ0 | 否 | LZ0 | .lzo | 是 | 需要建索引,还需要指定输入格式 | Snappy | 是 | Snappy | .snappy | 否 | 不需要 |
压缩性能比较
压缩算法 | 原始文件大小 | 压缩文件大小 | 压缩速度 | 解压速度 |
---|
gzip | 8.3GB | 1.8GB | 17.5MB/s | 58MB/s | bzip | 8.3GB | 1.1GB | 2.4MB/s | 9.5MB/s | LZ0 | 8.3GB | 2.9GB | 49.3MB/s | 74.6MB/s |
4.3 压缩方式选择
压缩方式选择时重点考虑:压缩/解压缩速度、压缩率(压缩后存储大小)、压缩后是否可以支持切片。
4.3.1 Gzip压缩
优点:压缩率比较高; 缺点:不支持 Split;压缩/解压速度一般;
4.3.2 Bzip2 压缩
优点:压缩率高;支持 Split; 缺点:压缩/解压速度慢。
4.3.3 Lzo 压缩
优点:压缩/解压速度比较快;支持 Split; 缺点:压缩率一般;想支持切片需要额外创建索引。
4.3.4 Snappy 压缩
优点:压缩和解压缩速度快; 缺点:不支持 Split;压缩率一般;
4.3.5 压缩位置选择
压缩可以在 MapReduce 作用的任意阶段启用。
4.4 压缩参数配置
压缩格式 | 对应的编码/解码器 |
---|
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec | gzip | org.apache.hadoop.io.compress.GzipCodec | bzip2 | org.apache.hadoop.io.compress.BZip2Codec | LZO | com.hadoop.compression.lzo.LzopCodec | Snappy | org.apache.hadoop.io.compress.SnappyCodec |
要在Hadoop中启用压缩,配置
参数 | 默认值 | 阶段 | 建议 |
---|
io.compression.codecs (在 core-site.xml 中配置) | 无,这个需要在命令行输入 hadoop checknative 查看 | 输入压缩 | Hadoop 使用文件扩展 名判断是否支持某种 | 编解码器 | | | | mapreduce.map.output.compr ess(在 mapred-site.xml 中 | | | | 配置) | false | mapper 输出 | 这个参数设为 true 启 用压缩 | mapreduce.map.output.compr ess.codec(在 mapred-site.xml 中配置) | org.apache.hadoop.io.com press.DefaultCodec | mapper 输出 | 企业多使用 LZO 或 Snappy 编解码器在此 | 阶段压缩数据 | | | | mapreduce.output.fileoutpu tformat.compress(在 | | | | mapred-site.xml 中配置) | false | reducer 输出 | 这个参数设为 true 启 用压缩 | mapreduce.output.fileoutpu tformat.compress.codec(在 | | | | mapred-site.xml 中配置) | org.apache.hadoop.io.com press.DefaultCodec | reducer 输出 | 使用标准工具或者编 解码器,如 gzip 和 | bzip2 | | | |
第5章 常见错误及解决方案
-
导包容易出错。尤其 Text 和 CombineTextInputFormat。 -
Mapper 中第一个输入的参数必须是 LongWritable 或者 NullWritable,不可以是 IntWritable. 报的错误是类型转换异常。 -
java.lang.Exception: java.io.IOException: Illegal partition for 13926435656 (4),说明 Partition和 ReduceTask 个数没对上,调整 ReduceTask 个数。 -
如果分区数不是 1,但是 reducetask 为 1,是否执行分区过程。答案是:不执行分区过程。因为在 MapTask 的源码中,执行分区的前提是先判断 ReduceNum 个数是否大于 1。不大于1 肯定不执行。 -
缓存 pd.txt 小文件案例中,报找不到 pd.txt 文件 原因:大部分为路径书写错误。还有就是要检查 pd.txt.txt 的问题。还有个别电脑写相对路径找不到 pd.txt,可以修改为绝对路径。 -
报类型转换异常。 通常都是在驱动函数中设置 Map 输出和最终输出时编写错误。Map 输出的 key 如果没有排序,也会报类型转换异常。 -
集群中运行 wc.jar 时出现了无法获得输入文件。 原因:WordCount 案例的输入文件不能放用 HDFS 集群的根目录。 -
自定义Outputformat时,注意在RecordWirter中的close方法必须关闭流资源。否则输出的文件内容中数据为空。
|