HDFS
什么是HDFS
它是一个分布式文件系统,适合一次写入多次读出。
不支持文件的随机读写,支持对文件的追加。原因:HDFS在存储文件时,以块的形式存储。如果随机写入,为保证块的顺序,所有内容都要后移(类似于数组),第一块写入一个文件,后面所有块的内容都要后移,会造成大量的网络Io(在hadoop中是非常宝贵的资源)和磁盘Io。而追加的话,直接在最后一块上追加即可。
HDFS文件的块
在Hadoop中一个文件被存放在多个块之中,每个块默认的大小为128M,如果一个文件不足128m,他同样占据一个块,如果超过128m它将被分割,除最后一个块以外,每一块的大小都是128m,最后一个块的大小小于128m。
为什么是128m
首先128m是一个人为定义的值,在Hadoop中可以通过修改hdfs.site.xml进行修改,考虑到校验机制,一定要是4bytes的倍数。
dfs.blocksize
134217728
The default block size for new files, in bytes.
You can use the following suffix (case insensitive):
k(kilo), m(mega), g(giga), t(tera), p(peta), e(exa) to specify the size (such as 128k, 512m, 1g, etc.),
Or provide complete size in bytes (such as 134217728 for 128 MB).
一个块是存储在磁盘上,因此在读写时,需要先寻址再读写。研究表明,一次最有性价比的传输是:寻址时间/传输时间=1%。寻址时间为10ms,因此传输时间控制在1s为好,在现有磁盘速度为100M/s时,设置128是最好的,如果磁盘读写速度达到500m,那可以设置512m;
但无论磁盘速度如何,块大小都不能过大或过小
- 过大
- :hadoop运行在廉价机器,太大,如果一个块在传输时出错,重传的代价太高
- :
- :map阶段一次默认处理一个块(一次处理一个块map的效率高),如果切片太大,map阶段的运行效率低。
- 过小
- :最直接的影响就是会增加NN的负担,NN中存储数据的元数据,过小的块会导致块的数量激增,NN需要处理的元数据信息过多,服役能力下降。元数据信息有(权限信息 Last Modified Replication Block SizeN ame -rw-r--r-- jueshali supergroup 273.81 MB tiem 备份数 文件大小 文件名),还有就是每一块在那台机器上。
- :块太小,每次读取一个块都要一定的寻址时间,效率低。
- ps hadoop不适合存储小文件
- :小文件太多,NN受不了
- :小文件在读写时,性价比太低,量少信息量低。
HDFS的Shell操作
hadoop fs + 命令
输入 hadoop fs会出现如下信息(命令)
Usage: hadoop fs [generic options]
[-appendToFile ... ] \\向文件追加
*[-cat [-ignoreCrc] ...] \\查看文件
[-checksum ...] \\计算文件校验
*[-chgrp [-R] GROUP PATH...] \\changeGroup
[-chmod [-R] PATH...] \\权限设置
[-chown [-R] [OWNER][:[GROUP]] PATH...] \\所有者设置
*[-copyFromLocal [-f] [-p] [-l] ... ]\\从本地上传
*[-copyToLocal [-p] [-ignoreCrc] [-crc] ... ]\\从hdfs下载到本地
[-count [-q] [-h] ...]\\计数
[-cp [-f] [-p | -p[topax]] ... ]\\复制
[-createSnapshot []]\\ 对一个snapshottable目录创建snapshot,snapshot可以用于数据的backup,避免用户错误和灾难恢复
[-deleteSnapshot ]\\ 删除快照
[-df [-h] [ ...]]\\ 显示磁盘使用情况
[-du [-s] [-h] ...]\\ 显示目录或者文件的大小
[-expunge]\\接受一个源目录和一个目标文件作为输入,并且将源目录中所有的文件连接成本地目标文件。?
[-find ... ...]\\
[-get [-p] [-ignoreCrc] [-crc] ... ]\\下载
[-getfacl [-R] ]\\获取副本数
[-getmerge [-nl] ]\\下载后合并
[-help [cmd ...]]\\帮助
[-ls [-d] [-h] [-R] [ ...]]\\ 类似于ls
[-mkdir [-p] ...]\\创建文件夹
[-moveFromLocal ... ]\\移动
[-moveToLocal ]\\移动
[-mv ... ]\\移动允许多个源路径
[-put [-f] [-p] [-l] ... ]\\上传
[-renameSnapshot ]\\快照命名
*[-rm [-f] [-r|-R] [-skipTrash] ...]删除
[-rmdir [--ignore-fail-on-non-empty] ...]\\删除文件夹
*[-setfacl [-R] [{-b|-k} {-m|-x } ]|[--set ]]\\改变一个文件的副本系数。-R选项用于递归改变目录下所有文件的副本系数。
[-setfattr {-n name [-v value] | -x name} ]\\
[-setrep [-R] [-w] ...]\\
[-stat [format] ...]\\返回指定路径的统计信息。
[-tail [-f] ]\\将文件尾部1K字节的内容输出到stdout。支持-f选项,行为和Unix中一致。
[-test -[defsz] ]\\检查文件的信息,-e,-z,-d
[-text [-ignoreCrc] ...]\\将源文件输出为文本格式。允许的格式是zip
[-touchz ...]\\创建一个0字节的空文件。
[-truncate [-w] ...]\\
[-usage [cmd ...]]\\
HDFS的java程序进行操作
操作之前的配置
- windows本机要有Hadoop:安装在一个纯英文文件目录下,并设置环境变量,环境变量设置到/bin就ok了
- idea开启后要配置Maven和建立一个log4j.properties
4.0.0
lpc.com
mapreduce
1.0-SNAPSHOT
junit
junit
RELEASE
org.apache.logging.log4j
log4j-core
2.8.2
org.apache.hadoop
hadoop-common
2.7.2
org.apache.hadoop
hadoop-client
2.7.2
org.apache.hadoop
hadoop-hdfs
2.7.2
# 配置日志
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
HDFS初始操作和结束操作
在使用java程序对HDFS进行操作时,我们首先要创造一个FileSystem的对象,这个对象可以通过类方法.get()获得,输入的参数就是文件系统的配置信息,配置信息从配置文件或者代码中读取。在使用后作为一个文件系统需要对其进行关闭
@Before
public void init() throws IOException {
// 在创建时会读取配置文件,配置文件不只一个,读取顺序为(1)客户端代码中设置的值 >(2)ClassPath下的用户自定义配置文件 >(3)然后是服务器的默认配置
Configuration conf = new Configuration();
//这里设置fs.defaultFS的值为hdfs。后面的没设置就是默认的本地文件系统,
conf.set("fs.defaultFS","hdfs://hadoop101:9000");
fs = FileSystem.get(conf);
//建立一个本地的文件系统用于测试
fsLocal = FileSystem.get(new Configuration());
}
@After
public void close() throws Exception{
IOUtils.closeStream(in);
IOUtils.closeStream(out);
if (fs!=null){
fs.close();
}
if (fsLocal!=null){
fsLocal.close();
}
}
HDFS文件上传和下载
文件的上传下载实际上从代码上看比较容易,调用copyFromLocalFile 和CopytoLocalFile即可
@Test
public void uploacalToHdfs() throws Exception{
Path src = new Path("H:/testfile.txt");
Path des = new Path("/hadoopday2/input");
// 第一个false是:是否删除源文件,第二个ture为是否对目标文件进行覆盖
fs.copyFromLocalFile(false, true,src,des);
}
@Test
public void downloadFromHdfs() throws Exception{
Path src = new Path("/hadoopday2/input");
Path des = new Path("H:/input1");
//第一个false为是否删除源文件,第二个false
fs.copyToLocalFile(false,src,des,false);
fs.copyToLocalFile();
}
通过流进行可操作的文件上传和下载
这一部分是通过自定义的流进行文件的上传和下载,在fs本身提供的方法也是通过流进行的操作,代码如下
public static boolean copy(FileSystem srcFS, FileStatus srcStatus,
FileSystem dstFS, Path dst,
boolean deleteSource,
boolean overwrite,
Configuration conf) throws IOException {
Path src = srcStatus.getPath();
dst = checkDest(src.getName(), dstFS, dst, overwrite);
if (srcStatus.isDirectory()) {
checkDependencies(srcFS, src, dstFS, dst);
if (!dstFS.mkdirs(dst)) {
return false;
}
FileStatus contents[] = srcFS.listStatus(src);
for (int i = 0; i < contents.length; i++) {
copy(srcFS, contents[i], dstFS,
new Path(dst, contents[i].getPath().getName()),
deleteSource, overwrite, conf);
}
} else {
InputStream in=null;
OutputStream out = null;
try {
in = srcFS.open(src);
out = dstFS.create(dst, overwrite);
IOUtils.copyBytes(in, out, conf, true);
} catch (IOException e) {
IOUtils.closeStream(out);
IOUtils.closeStream(in);
throw e;
}
}
if (deleteSource) {
return srcFS.delete(src, true);
} else {
return true;
}
}
从这里可以看出来,核心代码是IOUtils.copyBytes(in, out, conf, true);
这个IOUtils我自己还实现过,底层就是in.read()和out.write()
,当然其中还有关闭,判断为空,使用shuffle,综合以上,我实现的自定以上传下载如下。
@Test
public void myDownload() throws Exception{
//流操作
in = fs.open(new Path("/HDFS.txt"));
out = fsLocal.create(new Path("H:/HDFSfromHDFS.txt"));
byte [] buffer = new byte[1024];
for (int i = 0; i < 10; i++) {
System.out.println(i);
in.read(buffer);
out.write(buffer);
}
}
/**
* 用于测试一定大小的数据的上传
* @throws Exception
*/
@Test
public void myUpload() throws Exception{
in = fsLocal.open(new Path("H:/HDFS笔记.txt"));
out = fs.create(new Path("/HDFS.txt"));
// IOUtils.copyBytes(in,out,2048);
byte[] buffer =new byte[1024];
for (int i = 0; i <3; i++) {
in.read(buffer);
out.write(buffer);
}
}
代码不难,指得注意的是open可以创建一个输入流,creare可以创建一个输出流。使用seek可以设置流的偏移量
HDFS文件信息的查看
在HDFS中,文件的元数据包括文件长度,块大小,复本,修改时间,所有者和权限信息,这些信息封装在FileStatus对象之中。该对象可以通过FileSystem.getFileStatus()
获得。获得一个目录下的所有文件使用fs.listStatus(dir)
我的代码为打印两层目录,可以使用递归扩展
public void Status() throws Exception{
// 查看文件的信息
Path dir = new Path("/user/Administrator/jdk.gz");
FileStatus fileStatus = fs.getFileStatus(dir);
System.out.println("是否文件夹:"+ fileStatus.isDirectory());
System.out.println("长度是"+fileStatus.getLen());
System.out.println("块大小是"+fileStatus.getBlockSize());
//获取文件名
System.out.println("文件名是"+fileStatus.getPath().getName());
}
@Test
public void blockStatus() throws Exception{
Path dir = new Path("/user/Administrator/jdk.gz");
LocatedFileStatus status;
//调用listLocatedStatus方法会返回一个迭代器,迭代器中的存的就是LocatedFileStatus对象;
RemoteIterator statuss = fs.listLocatedStatus(dir);
while (statuss.hasNext()){
status = statuss.next();
BlockLocation[] blockLocations = status.getBlockLocations();
for (BlockLocation blockLocation:blockLocations) {
System.out.println(blockLocation);
}
System.out.println(status.isDirectory()?"是目录":"不是目录");
}
}
public void Statuss() throws Exception{
Path dir = new Path("/hadoopday2");
FileStatus[] fileStatusess = fs.listStatus(dir);
for (FileStatus filestatus :
fileStatusess) {
System.out.println(filestatus.getPath().getName());
if(filestatus.isDirectory()){
Path nextDir = filestatus.getPath();
FileStatus[] nextFileStatusess = fs.listStatus(nextDir);
for (FileStatus nextFilestatus :
nextFileStatusess) {
System.out.println("----"+nextFilestatus.getPath().getName());
}
}
}
}
HDFS块信息的处理
块和备份我认识HDFS文件系统中及其重要的一部分,通过listLocatedStatus可以获取block列表的迭代器,BlockLocation
对象中主要存储块所在的位置,大小,偏移量等。
@Test
public void blockStatus() throws Exception{
Path dir = new Path("/user/Administrator/jdk.gz");
LocatedFileStatus status;
RemoteIterator statuss = fs.listLocatedStatus(dir);
while (statuss.hasNext()){
status = statuss.next();
BlockLocation[] blockLocations = status.getBlockLocations();
for (BlockLocation blockLocation:blockLocations) {
System.out.println(blockLocation);
}
System.out.println(status.isDirectory()?"是目录":"不是目录");
}
}
练习,HDFS的分块下载
public void downWithBlock() throws Exception{
Path src = new Path("jdk.gz");
String basic = "H:/block";
in = fs.open(src);
byte[] buffer = new byte[1024];
//获取块大小,
long offset= fs.getFileStatus(src).getBlockSize();
//获取块数量
int blockNum = fs.listLocatedStatus(src).next().getBlockLocations().length;
System.out.println(offset + "" + blockNum);
for (int i = 0; i < blockNum; i++) {
in.seek(i*offset);
Path des = new Path(basic+i);
out = fsLocal.create(des);
if (i==blockNum-1){
IOUtils.copyBytes(in,out,1024);
}else{
for (int j = 0;j < (int)offset/1024;j++){
in.read(buffer);
out.write(buffer);
}
}
}
}