HDFS进阶笔记2
8. Hadoop联邦
8.1 为什么需要联邦
- 虽然HDFS HA解决了“单点故障”问题,但HDFS在扩展性、整体性能和隔离性方面仍有问题
- 系统扩展性方面,元数据存储在NN内存中,受限于内存上限(每个文件、目录、block占用约150字节)
- 整体性能方面,吞吐量受单个NN的影响
- 隔离性方面,一个程序可能会影响其他程序的运行,如果一个程序消耗过多资源会导致其他程序无法顺利运行
- HDFS HA本质上还是单名称节点
8.2 联邦
- HDFS联邦可以解决以上三个问题
- HDFS联邦中,设计了多个命名空间;每个命名空间有一个NN或一主一备两个NN,使得HDFS的命名服务能够水平扩展
- 这些NN分别进行各自命名空间namespace和块的管理,相互独立,不需要彼此协调
- 每个DN要向集群中所有的NN注册,并周期性的向所有NN发送心跳信息和块信息,报告自己的状态
- HDFS联邦每个相互独立的NN对应一个独立的命名空间
- 每一个命名空间管理属于自己的一组块,这些属于同一命名空间的块对应一个“块池”的概念。
- 每个DN会为所有块池提供块的存储,块池中的各个块实际上是存储在不同DN中的
8.3 扩展
8.4 小结
9. 文件压缩
9.1 压缩算法
-
文件压缩好处:
- 减少数据所占用的磁盘空间
- 加快数据在磁盘、网络上的IO
-
常用压缩格式
压缩格式 UNIX工具 算 法 文件扩展名 可分割 DEFLATE 无 DEFLATE .deflate No gzip gzip DEFLATE .gz No zip zip DEFLATE .zip YES bzip bzip2 bzip2 .bz2 YES LZO lzop LZO .lzo No Snappy 无 Snappy .snappy No -
Hadoop的压缩实现类;均实现CompressionCodec接口
压缩格式 对应的编码/解码器 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@node01 ~]$ hadoop checknative
9.2 编程实践
-
编程:上传压缩过的文件到HDFS
- 对CopyFileFromLocal代码做修改,向文件压缩后,再上传到HDFS
- 代码
package com.wph.hadoop.compress;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;
import java.io.*;
import java.net.URI;
/**
* 将本地文件系统的文件通过java-API写入到HDFS文件,并且写入时使用压缩
*/
public class FileCopyFromLocal {
public static void main(String[] args) throws ClassNotFoundException {
//压缩相关
//压缩类
String codecClassName = "org.apache.hadoop.io.compress.BZip2Codec";
Class<?> codecClass = Class.forName(codecClassName);
//HDFS读写的配置文件
Configuration conf = new Configuration();
//java反射机制生成压缩类对象
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
String source="/home/hadoop/edit.xml"; //linux中的文件路徑,demo存在一定数据
//先确保/data目录存在
String destination="hdfs://node01:9000/copyFromLocal/edit0904.xml.bz2";//HDFS的路徑
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(source));
FileSystem fs = FileSystem.get(URI.create(destination),conf);
//调用Filesystem的create方法返回的是FSDataOutputStream对象
//该对象不允许在文件中定位,因为HDFS只允许一个已打开的文件顺序写入或追加
OutputStream out = fs.create(new Path(destination));
//对输出流的数据压缩
CompressionOutputStream compressedOut = codec.createOutputStream(out);
//流拷贝
IOUtils.copyBytes(in, compressedOut, 4096, true);
} catch (FileNotFoundException e) {
System.out.println("exception");
e.printStackTrace();
} catch (IOException e) {
System.out.println("exception1");
e.printStackTrace();
}
}
}
10. 小文件治理
10.1 有没有问题
- NameNode存储着文件系统的元数据,每个文件、目录、块大概有150字节的元数据;
- 因此文件数量的限制也由NN内存大小决定,如果小文件过多则会造成NN的压力过大
- 且HDFS能存储的数据总量也会变小
10.2 HAR文件方案
- 本质启动mr程序,所以需要启动yarn
用法:archive -archiveName .har -p [-r ]*
# 创建archive文件;/testhar有两个子目录th1、th2;两个子目录中有若干文件
hadoop archive -archiveName test.har -p /testhar -r 3 th1 th2 /outhar # 原文件还存在,需手动删除
# 查看archive文件
hdfs dfs -ls -R har:///outhar/test.har
# 解压archive文件
# 方式一
hdfs dfs -cp har:///outhar/test.har/th1 hdfs:/unarchivef # 顺序
hadoop fs -ls /unarchivef
# 方式二
hadoop distcp har:///outhar/test.har/th1 hdfs:/unarchivef2 # 并行,启动MR
10.3 Sequence Files方案
-
SequenceFile文件,主要由一条条record记录组成;每个record是键值对形式的
-
SequenceFile文件可以作为小文件的存储容器;
- 每条record保存一个小文件的内容
- 小文件名作为当前record的键;
- 小文件的内容作为当前record的值;
- 如10000个100KB的小文件,可以编写程序将这些文件放到一个SequenceFile文件。
-
一个SequenceFile是可分割的,所以MapReduce可将文件切分成块,每一块独立操作。
-
具体结构:
- 一个SequenceFile首先有一个4字节的header(文件版本号)
- 接着是若干record记录
- 记录间会随机的插入一些同步点sync marker,用于方便定位到记录边界
-
不像HAR,SequenceFile支持压缩。记录的结构取决于是否启动压缩
- 支持两类压缩:
- 不压缩NONE
- 压缩RECORD
- 压缩BLOCK,①一次性压缩多条记录;②每一个新块Block开始处都需要插入同步点;如下图
- 在大多数情况下,以block(注意:指的是SequenceFile中的block)为单位进行压缩是最好的选择
- 因为一个block包含多条记录,利用record间的相似性进行压缩,压缩效率更高
- 把已有的数据转存为SequenceFile比较慢。比起先写小文件,再将小文件写入SequenceFile,一个更好的选择是直接将数据写入一个SequenceFile文件,省去小文件作为中间媒介.
- 支持两类压缩:
-
向SequenceFile写入数据
package com.wph.hadoop.sequencefile;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.BZip2Codec;
import java.io.IOException;
import java.net.URI;
public class SequenceFileWriteNewVersion {
//模拟数据源
private static final String[] DATA = {
"The Apache Hadoop software library is a framework that allows for the distributed processing of large data sets across clusters of computers using simple programming models.",
"It is designed to scale up from single servers to thousands of machines, each offering local computation and storage.",
"Rather than rely on hardware to deliver high-availability, the library itself is designed to detect and handle failures at the application layer",
"o delivering a highly-available service on top of a cluster of computers, each of which may be prone to failures.",
"Hadoop Common: The common utilities that support the other Hadoop modules."
};
public static void main(String[] args) throws IOException {
//输出路径:要生成的SequenceFile文件名
String uri = "hdfs://node01:9000/writeSequenceFile";
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri), conf);
//向HDFS上的此SequenceFile文件写数据
Path path = new Path(uri);
//因为SequenceFile每个record是键值对的
//指定key类型
IntWritable key = new IntWritable();
//指定value类型
Text value = new Text();
//
// FileContext fileContext = FileContext.getFileContext(URI.create(uri));
// Class<?> codecClass = Class.forName("org.apache.hadoop.io.compress.SnappyCodec");
// CompressionCodec SnappyCodec = (CompressionCodec)ReflectionUtils.newInstance(codecClass, conf);
// SequenceFile.Metadata metadata = new SequenceFile.Metadata();
// //writer = SequenceFile.createWriter(fs, conf, path, key.getClass(), value.getClass());
// writer = SequenceFile.createWriter(conf, SequenceFile.Writer.file(path), SequenceFile.Writer.keyClass(IntWritable.class),
// SequenceFile.Writer.valueClass(Text.class));
//创建向SequenceFile文件写入数据时的一些选项
//要写入的SequenceFile的路径
SequenceFile.Writer.Option pathOption = SequenceFile.Writer.file(path);
//record的key类型选项
SequenceFile.Writer.Option keyOption = SequenceFile.Writer.keyClass(IntWritable.class);
//record的value类型选项
SequenceFile.Writer.Option valueOption = SequenceFile.Writer.valueClass(Text.class);
//SequenceFile压缩方式:NONE | RECORD | BLOCK三选一
//方案一:RECORD、不指定压缩算法
SequenceFile.Writer.Option compressOption = SequenceFile.Writer.compression(SequenceFile.CompressionType.RECORD);
SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressOption);
//方案二:BLOCK、不指定压缩算法
// SequenceFile.Writer.Option compressOption = SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK);
// SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressOption);
//方案三:使用BLOCK、压缩算法BZip2Codec;压缩耗时间
//再加压缩算法
// BZip2Codec codec = new BZip2Codec();
// codec.setConf(conf);
// SequenceFile.Writer.Option compressAlgorithm = SequenceFile.Writer.compression(SequenceFile.CompressionType.RECORD, codec);
// //创建写数据的Writer实例
// SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressAlgorithm);
for (int i = 0; i < 100000; i++) {
//分别设置key、value值
key.set(100 - i);
value.set(DATA[i % DATA.length]);
System.out.printf("[%s]\t%s\t%s\n", writer.getLength(), key, value);
//在SequenceFile末尾追加内容
writer.append(key, value);
}
//关闭流
IOUtils.closeStream(writer);
}
}
- 命令查看SequenceFile内容
hadoop fs -text /writeSequenceFile
- 读取SequenceFile文件
package com.wph.hadoop.sequencefile;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.util.ReflectionUtils;
import java.io.IOException;
public class SequenceFileReadNewVersion {
public static void main(String[] args) throws IOException {
//要读的SequenceFile
String uri = "hdfs://node01:9000/writeSequenceFile";
Configuration conf = new Configuration();
Path path = new Path(uri);
//Reader对象
SequenceFile.Reader reader = null;
try {
//读取SequenceFile的Reader的路径选项
SequenceFile.Reader.Option pathOption = SequenceFile.Reader.file(path);
//实例化Reader对象
reader = new SequenceFile.Reader(conf, pathOption);
//根据反射,求出key类型
Writable key = (Writable)
ReflectionUtils.newInstance(reader.getKeyClass(), conf);
//根据反射,求出value类型
Writable value = (Writable)
ReflectionUtils.newInstance(reader.getValueClass(), conf);
long position = reader.getPosition();
System.out.println(position);
while (reader.next(key, value)) {
String syncSeen = reader.syncSeen() ? "*" : "";
System.out.printf("[%s%s]\t%s\t%s\n", position, syncSeen, key, value);
position = reader.getPosition(); // beginning of next record
}
} finally {
IOUtils.closeStream(reader);
}
}
}
11. 文件快照
11.1 什么是快照
- 快照比较常见的应用场景是数据备份,以防一些用户错误或灾难恢复
- 快照snapshots是HDFS文件系统的,只读的、某时间点的拷贝
- 可以针对某个目录,或者整个文件系统做快照
- 创建快照时,block块并不会被拷贝。快照文件中只是记录了block列表和文件大小,不会做任何数据拷贝
11.2 快照操作
-
允许快照
允许一个快照目录被创建。如果这个操作成功完成,这个目录就变成snapshottable
用法:hdfs dfsadmin -allowSnapshot
hdfs dfsadmin -allowSnapshot /wordcount
-
禁用快照
用法:hdfs dfsadmin -disallowSnapshot
hdfs dfsadmin -disallowSnapshot /wordcount
-
创建快照
用法:hdfs dfs -createSnapshot []
#注意:先将/wordcount目录变成允许快照的 hdfs dfs -createSnapshot /wordcount wcSnapshot
-
查看快照
hdfs dfs -ls /wordcount/.snapshot
-
重命名快照
这个操作需要拥有snapshottabl目录所有者权限
用法:hdfs dfs -renameSnapshot
hdfs dfs -renameSnapshot /wordcount wcSnapshot newWCSnapshot
-
用快照恢复误删除数据
HFDS的/wordcount目录,文件列表下
误删除/wordcount/edit.xml文件
hadoop fs -rm /wordcount/edit.xml
恢复数据
hadoop fs -cp /wordcount/.snapshot/newWCSnapshot/edit.xml /wordcount
-
删除快照
这个操作需要拥有snapshottabl目录所有者权限
用法:hdfs dfs -deleteSnapshot
hdfs dfs -deleteSnapshot /wordcount newWCSnapshot