一、数据压缩
1. MR支持的压缩编码
压缩格式 | hadoop是否自带 | 使用算法 | 扩展名 | 是否可切片 | 压缩文件作为输入,是否需要单独配置 | 特点 |
DEFLATE | 是 | DEFLATE | .deflate | 否 | 不需要 | 压缩率较高 解压缩速度较快 |
Gzip | 是 | DEFLATE | .gz | 否 | 不需要 | 压缩率较高 解压缩速度较快 |
bzip2 | 是 | bzip2 | .bz2 | 是 | 不需要 | 压缩率最高 解压缩速度最慢 |
LZO | 否 | LZO | .lzo | 是 | 需要 | 压缩率适中 解压缩速度很快 |
Snappy | 是 | Snappy | .snappy | 否 | 不需要 | 压缩率适中 解压缩速度最快 |
2. 压缩格式与编解码器
压缩格式 | 编解码器类 |
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 |
3. 压缩位置选择
4. 压缩参数配置
阶段 | 配置 位置 | 配置项 | 默认值 | 配置值 |
Mapper输出 | mapred-site.xml | mapreduce.map.output.compress mapreduce.map.output.compress.codec | false org.apache.hadoop.io.compress.DefaultCodec | true 编解码器类 |
Reducer输出 | mapred-site.sml | mapreduce.output.fileoutputformat.compress mapreduce.output.fileoutputformat.compress.codec | false org.apache.hadoop.io.compress.DefaultCodec | true 编解码器类 |
5. 压缩案例
(1) Mapper输出阶段开启压缩
a. 在配置文件中直接配置。配置后,所有的MR都会采用Mapper输出阶段压缩;
b. 在MR代码中通过配置文件对象(Configuration)指定
conf.set("mapreduce.map.output.compress", "true");
conf.set("mapreduce.map.output.compress.codec", "要使用的编解码器类");
(2) Reducer输出阶段开启压缩
a. 在配置文件中直接配置。配置后,所有的MR都会采用Reducer输出阶段压缩;
b. 在MR代码中通过配置文件对象(Configuration)指定
conf.set("mapreduce.output.fileoutputformat.compress", "true");
conf.set("mapreduce.output.fileoutputformat.compress.codec", "要使用的编解码器类");
6. 封装使用Hadoop的解压缩技术
(1) 封装工具类
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.*;
import org.apache.hadoop.util.ReflectionUtils;
import java.io.*;
public class CompressUtils {
// 枚举类:含有创建压缩类编解码器的必要信息
public enum CodecType{
/**
* 5个解压缩方案枚举
*/
DEFLATE(DeflateCodec.class, ".deflate"),
GZIP(GzipCodec.class, ".gz"),
BZIP2(BZip2Codec.class, ".bz2"),
LZOP("com.hadoop.compression.lzo.LzopCodec", ".lzo"),
SNAPPY(SnappyCodec.class, ".snappy")
;
// 压缩类名
private String codecClassname;
// 压缩类
private Class codecClass;
// 后缀名
private String suffix;
// 含参构造器,使用类对象构造
CodecType(Class codecClass, String suffix){
this(codecClass.getName(), suffix);
this.codecClass = codecClass;
}
// 含参构造器,使用类名构造
CodecType(String codecClassname, String suffix){
this.codecClassname = codecClassname;
this.suffix = suffix;
}
/**
* 获取解压缩类
* @return 类
*/
public Class getCodecClass(){
return this.codecClass;
}
/**
* 获取解压缩全限定类名
* @return 类名
*/
public String getCodecClassname(){
return this.codecClassname;
}
/**
* 获取压缩文件对应的后缀名
* @return 后缀
*/
public String getSuffix(){
return this.suffix;
}
}
// 内置异常类
public static class CompressionException extends Exception{
public CompressionException(String tip){
super(tip);
}
}
/**
* 压缩本地文件
* @param sourceFile 源文件名称,包含后缀名
* @param destFile 压缩文件名称,不包含后缀名
* @param codec 编解码器枚举对象
* @throws CompressionException
*/
public static void compress(String sourceFile, String destFile, CodecType codec) throws CompressionException {
try {
compress(new FileInputStream(new File(sourceFile)), new FileOutputStream(new File(destFile + codec.getSuffix())), codec);
} catch (FileNotFoundException e) {
throw new CompressionException("源文件:" + sourceFile + "不存在");
}
}
/**
* 压缩本地/集群文件
* @param src 源文件输入流
* @param dest 压缩文件输出流,创建输出流对象的时候,目标文件名包含后缀名
* @param codec 编解码器枚举对象
* @throws CompressionException
*/
public static void compress(InputStream src, OutputStream dest, CodecType codec) throws CompressionException {
compress(src, dest, new Configuration(), codec);
}
/**
* 压缩本地/集群文件
* @param src 源文件输入流
* @param dest 压缩文件输出流,创建输出流对象的时候,目标文件名包含后缀名
* @param conf 配置文件对象
* @param codec 编解码器枚举对象
* @throws CompressionException
*/
public static void compress(InputStream src, OutputStream dest, Configuration conf, CodecType codec) throws CompressionException {
// 获取编解码器,并且获取对应的输出流
Class codecClass = codec.getCodecClass();
if (codecClass == null){
try {
codecClass = Class.forName(codec.getCodecClassname());
} catch (ClassNotFoundException e) {
throw new CompressionException("类:" + codec.getCodecClassname() + "未找到");
}
}
CompressionCodec compressionCodec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
CompressionOutputStream outputStream = null;
try {
outputStream = compressionCodec.createOutputStream(dest);
// 输出压缩文件
IOUtils.copyBytes(src, outputStream, conf);
} catch (IOException e) {
throw new CompressionException("IO资源紧缺");
} finally {
IOUtils.closeStreams(src, outputStream);
}
}
/**
* 解压缩本地文件
* @param srcFile 源文件
* @param destFile 目的文件
* @param codec 编解码器枚举对象
* @throws CompressionException
*/
public static void decompress(String srcFile, String destFile, CodecType codec) throws CompressionException {
try {
decompress(new FileInputStream(new File(srcFile)), new FileOutputStream(new File(destFile)), codec);
} catch (FileNotFoundException e) {
throw new CompressionException("源文件:" + srcFile + "不存在");
}
}
/**
* 解压缩本地/集群文件
* @param src 源文件输入流
* @param dest 目的文件输出流
* @param codec 编解码器枚举对象
* @throws CompressionException
*/
public static void decompress(InputStream src, OutputStream dest, CodecType codec) throws CompressionException {
decompress(src, dest, new Configuration(), codec);
}
/**
* 解压缩本地/集群文件
* @param src 源文件输入流
* @param dest 目的文件输出流
* @param conf 配置文件对象
* @param codec 编解码器枚举对象
* @throws CompressionException
*/
public static void decompress(InputStream src, OutputStream dest, Configuration conf, CodecType codec) throws CompressionException {
// 获取编解码器,并且获得对应的输入流
Class codecClass = codec.getCodecClass();
if (codecClass == null){
try {
codecClass = Class.forName(codec.getCodecClassname());
} catch (ClassNotFoundException e) {
throw new CompressionException("类:" + codec.getCodecClassname() + "未找到");
}
}
CompressionCodec compressionCodec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
CompressionInputStream inputStream = null;
try {
inputStream = compressionCodec.createInputStream(src);
// 解压
IOUtils.copyBytes(inputStream, dest, conf);
} catch (IOException e) {
throw new CompressionException("IO资源紧缺");
}finally {
IOUtils.closeStreams(inputStream, dest);
}
}
}
(2) 使用示例
无需关闭开启的流,流会由工具类负责关闭
a. 压缩本地文件
try {
// 压缩本地文件
// 编解码器枚举对象
CodecType codec = CodecType.GZIP;
// 压缩
CompressUtils.compress("D:\\moudle\\hadoop_results\\mapred\\in\\jianai\\ja.txt", "D:\\moudle\\hadoop_results\\mapred\\in\\jianai\\ja", codec);
} catch (CompressionException e) {
e.printStackTrace();
}
b. 压缩集群文件
try {
// 压缩集群文件
// 编解码器枚举对象
CodecType codec = CodecType.GZIP;
// 获取连接集群的文件系统对象
FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop101:9820"), new Configuration(), "soro");
// 源文件输入流
FSDataInputStream inputStream = fs.open(new Path(URI.create("/input/ja.txt")));
// 目的文件输出流
FSDataOutputStream outputStream = fs.create(new Path(URI.create("/input/ja" + codec.getSuffix())), true);
// 压缩
CompressUtils.compress(inputStream, outputStream, fs.getConf(), codec);
} catch (CompressionException | IOException | InterruptedException e) {
e.printStackTrace();
}
c. 解压缩本地文件
try {
// 解压缩本地文件
// 编码器枚举对象
CodecType codec = CodecType.GZIP;
// 压缩文件路径
String path = "D:\\moudle\\hadoop_results\\mapred\\in\\jianai\\ja" + codec.getSuffix();
// 解压缩
CompressUtils.decompress(path, "D:\\moudle\\hadoop_results\\mapred\\in\\jianai\\ja1.txt", codec);
} catch (CompressionException e) {
e.printStackTrace();
}
d. 解压缩集群文件
try {
// 解压缩集群文件
// 编码器枚举对象
CodecType codec = CodecType.GZIP;
// 压缩文件路径
String path = "/input/ja" + codec.getSuffix();
// 获取连接集群的文件系统对象
FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop101:9820"), new Configuration(), "soro");
// 压缩文件输入流
FSDataInputStream inputStream = fs.open(new Path(URI.create(path)));
// 目的文件输出流
FSDataOutputStream outputStream = fs.create(new Path(URI.create("/input/ja1.txt")), true);
// 解压缩
CompressUtils.decompress(inputStream, outputStream, codec);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (CompressionException e) {
e.printStackTrace();
}
二、优化
1. 数据倾斜问题
(1) 导致原因:某些Reducer接受了特别多的数据,某些Reducer接受的数据较少,导致了数据倾斜;
(2) 解决方案:
a. 细化分区;
b. 启用Combiner,提前对数据做处理;
c. 如果是Join的场景,并且有一张表的数据量十分小,就放弃使用Reduce Join,去使用Map Join;
2. Mapper运行时间太长,Reducer等待过久
(1) 导致原因:默认情况下,Reducer会等待Mapper执行完毕后才会执行,当输入的数据量很大,会开启很多Mapper,此时Mapper需要花费较多时间执行,从而导致了Reducer等待时间过长;
(2) 解决方案:
配置位置 | 配置项 | 配置值 |
mapred-site.xml | mapreduce.job.reduce.slowstart.completedmaps | 数字。指定几个Mapper完成之后,就开始启动Reducer拷贝这些Mapper输出的文件 |
3. 小文件过多
(1) 导致原因:小文件过多,会导致NameNode耗费大量的存储空间维护所有小文件的元数据,也会导致在执行MR的时候,每个小文件单独切成一片,开启一个MapTask;
(2) 解决方案:
a. 在将小文件上传到HDFS之前,先将小文件打成一个包,然后再上传;
b. 小文件已经上传至HDFS,可以使用Hadoop Archive技术(使用方法,请查看下文中的新特性内容),将小文件归档;
c. 小文件已经上传至HDFS,使用TextCombineInputFormat;
4. Mapper阶段溢写次数太多
(1) 导致原因:处理文件较大、环形缓存冲内存较小,导致发生多次落盘溢写;
(2) 解决方案:
配置位置 | 配置项 | 配置值 |
mapred-site.xml | mapreduce.task.io.sort.mb | 数字、单位mb。环形缓冲区大小,默认100m。 |
mapred-site.xml | mapreduce.map.sort.spill.percent | 小数、范围:0~1。环形缓冲区溢写的阈值,默认为0.8 |
5. Mapper阶段Merge次数太多
(1) 导致原因:Mapper阶段溢写磁盘的文件较多,将这些文件不断归并的次数太多,从而导致效率缓慢;
(2) 解决方案:
配置位置 | 配置项 | 配置值 |
mapred-site.xml | mapreduce.task.io.sort.factor | 数字。当溢写多少个文件之后,进行Merge合并,默认为10.可以适当调大改参数,减少Merge次数 |
6. IO传输太慢
(1) 导致原因:Mapper输出的文件太大,导致Reducer拷贝的时候很慢;
(2) 解决方案:启用压缩。
7. 资源调优
(1) MR资源调优
配置位置 | 配置项 | 配置值 |
MR程序中或者mapred-site.xml | mapreduce.map.memory.mb | 一个MapTask可使用的资源上限(mb),超过该资源上限,会被强制杀死 |
MR程序中或者mapred-site.xml | mapreduce.map.cpu.vcores | 一个MapTask最多可使用的cpu个数,默认为1 |
MR程序中或者mapred-site.xml | mapreduce.reduce.memory.mb | 一个Reduce Task可使用的资源上限(mb),超过该资源上限,会被强制杀死 |
MR程序中或者mapred-site.xml | mapreduce.reduce.cpu.vcores | 一个Reduce Task最多可使用的cpu个数,默认为1 |
MR程序中或者mapred-site.xml | mapreduce.reduce.shuffle.parallelcopies | 一个Reducer能够并行读取几个Mapper输出的文件,默认为5 |
MR程序中或者mapred-site.xml | mapreduce.reduce.shuffle.input.buffer.percent | 小数,范围:0~1;Reducer中用来存储分区数据的内存占分配给Reducer的总内存的多少,默认为0.7。 |
MR程序中或者mapred-site.xml | mapreduce.reduce.shuffle.merge.percent | 小数,范围:0~1;Reducer存储分区数据的内存达到百分之多少就开始向磁盘写新来的分区数据,默认为0.66。 |
MR程序中或者mapred-site.xml | mapreduce.reduce.input.buffer.percent | 小数,范围:0~1;在reduce执行期间,分配相对于reduce总内存的百分之多少用来存储分区数据,只有占用比例小于配置值时,才会开启下一个reduce。默认为0.0。 |
(2) Yarn分配资源调优
配置位置 | 配置项 | 配置值 |
yarn-site.xml | yarn.scheduler.minimum-allocation-mb | 分配给Container的最小内存(物理内存+虚拟内存),默认值为:1024 |
yarn-site.xml | yarn.scheduler.maximum-allocation-mb | 分配给Container的最大内存(物理内存+虚拟内存),默认值为:8192 |
yarn-site.xml | yarn.scheduler.minimum-allocation-vcores | 每个Container申请的最小CPU数,默认值为1 |
yarn-site.xml | yarn.scheduler.maximum-allocation-vcores | 每个Container申请的最大CPU数,默认值为32 |
yarn-site.xml | yarn.nodemanager.resource.memory-mb | 给Container分配的最大物理内存,默认值为8192 |
8. 数据均衡(将数据平均分散)
(1) 节点间数据均衡
# 开启数据均衡命令
# 参数10的意思是:集群中各个节点的磁盘空间利用率相差不超过10%
start-balancer.sh -threshold 10
# 停止数据均衡命令
stop-balancer.sh
(2) 磁盘间数据均衡
# 生成均衡计划
hdfs diskbalancer -plan hadoop103
# 执行均衡计划
hdfs diskbalancer -execute hadoop103.plan.json
# 查看当前均衡任务的执行情况
hdfs diskbalancer -query hadoop103
# 取消均衡任务
hdfs diskbalancer -cancel hadoop103.plan.json
三、便利新特性
1. 数据拷贝
(1) 将文件从集群的某一位置拷贝到另一个位置
# 语法格式:hadoop distcp src dest
# hdfs://为分布式文件系统网络传输协议名
hadoop distcp hdfs://hadoop101:9820/input/ja.gz hdfs://hadoop101:9820/input
(2) 将文件从集群拷贝到本地
# 语法格式为:hadoop distcp src dest
# hdfs://为分布式文件系统传输协议名,file://为本地文件系统传输协议名
hadoop distcp hdfs://hadoop101:9820/input/ja.gz file://hadoop101/home/soro
(3) 将文件从本地拷贝到集群
# 语法格式为:hadoop distcp src dest
# hdfs://为分布式文件系统传输协议名,file://为本地文件系统传输协议名
hadoop distcp file:///home/soro/test.txt hdfs://hadoop101:9820/input
(4) 将一个集群的文件拷贝到另一个集群上
# 语法格式为:hadoop distcp src dest
# hdfs://为分布式文件系统传输协议名
hadoop distcp hdfs://hadoop101:9820/input hdfs://hadoop102:9820/
2. Hadoop Archive(文件打包归档)
(1) 打包归档
# 语法格式:hadoop archive -archiveName 归档文件名称.har -p 要归档的文件 归档文件存放位置
hadoop archive -archiveName smallFile.har -p /smallFile/ /
(2) 查看har中的文件
a. 查看归档文件中的内容
# 查看归档文件中实际存储的真实内容
hdfs dfs -ls /smallFile.har
b. 查看归档文件中合并后的文件内容(查看part-0文件内容,其他一样的操作方式)
# 查看part-0文件的内容,也可查看其他文件内容
hdfs dfs -head /smallFile.har/part-0
c. 查看归档的文件列表
# 哪些文件被归档了
# har://为协议名
hdfs dfs -ls har:///smallFile.har
d. 查看各个被归档文件的内容
# 查看被归档的文件内容
# har://为协议名
hdfs dfs -head har:///smallFile.har/a.txt
e. 解档
# 将归档文件内容全部解出来
# 也可以解出单独的文件
# har://为协议名
hdfs dfs -cp har:///smallFile.har/* /smallFile
3. 回收站
(1) 回收站位置
/user/用户名/.Trash
(2) 在core-site.xml中添加配置
<!-- 回收站中的文件保存多长时间,单位为分钟 -->
<property>
<name>fs.trash.interval</name>
<value>1</value>
</property>
<!--
多久一次去检测回收站中是否有文件,单位为分钟,如果回收站中有文件就删除
一般配置该值>=fs.trash.interval
-->
<property>
<name>fs.trash.checkpoint.interval</name>
<value>1</value>
</property>
(3) 将回收站中的文件恢复
# 回收站中的文件也是存在hdfs上
# 将回收站中的文件恢复就是将回收站中的文件移动到hdfs集群上的其他地方
mv /user/用户名/.Trash/xxx.txt /