8、Hadoop分布式文件系统

来源:互联网 发布:软件专业就业前景 编辑:程序博客网 时间:2024/06/14 21:27

从今天开始学习《Hadoop权威指南》,希望学有所获。。。。

1、HDFS的设计

Hadoop的分布式文件系统被称为HDFS(Hadoop Distributed File System),是以流式数据访问模式存储超大文件而设计的文件系统,在商用硬件的集群上运行。其中,流式数据访问:HDFS是建立在一次写入,多次读取模式是最高效的思想基础上的。商用硬件是Hadoop不需要运行在昂贵、可靠的硬件上,它被设计运行在普通的商用硬件上,至少对于大的集群来说,节点的故障率还是比较高的,但是对于HDFS能够让任务继续进行为不让用户有明显的中断感觉。同时,有些领域不是适合HDFS,比如低延迟的数据访问、大量的小文件以及多用户写入,任意修改文件等。

2、Java接口

2.1、使用FileSystem API读取数据

FileSystem是一个普通的文件系统API,取得FileSystem实例有两种静态工厂方法:

public static FileSystem get(Configuration conf) throws IOExceptionpublic static FileSystem get(URI uri, Configuration conf) throws IOException
Configuration对象封装了一个客户端或服务器的配置,用从类路径读取来的配置文件(如conf/core-site.xml)来设置,第一个方法返回默认的文件系统(在conf/core-site.xml中设置,如果没有设置就返回本地文件系统);第二个方法使用指定的URI方案和决定所用文件系统的权限,如果指定URI中没有方案,就退回默认的文件系统。有了FileSystem实例后,调用open来得到文件的输入流:

public FSDataInputStream open(Path f) throws IOExceptionpublic abstract FSDataInputStream open(Path f, int bufferSize) throws IOException
利用这两个函数可以实现文件的输出显示:

public class FileSystemCat{    public void static main(String[] args) throws Exception{        String uri = args[0];        Configuration conf = new Configuration();        FileSystem fs = FileSysytem.get(URI.create(uri),conf);        InputStream in = null;        try{            in = fs.open(new Path(uri));            IOUtils.copyBytes(in.System.out,4096,false);        }finally{             IOUtils.closeStream(in);        }    }}
FileSystem中的open()方法实际返回的是FSDataInputStream类型,不是标准的java.io,这个类是java.io.DataInputStream的子类。支持随机访问,这样就可以在流的任意位置读取数据了。

public class FSDataInputStream extends DataInputStream implements Seekable, PositionReadable{        //implementation elided}其中:public interface Seekable{    void seek(long pos) throws IOException;    long getPos() throws IOException;    boolean seekToNewSource(long targetPos) throws IOException;}
FSDataInputStream也实现了PositionedReadable接口,从一个指定位置读取一部分数据:

public interface PositionedReadable{    public int read(long position,byte[] buffer, int offset, int length) throws IOException;    public void readFully(long position,byte[] buffer, int offset, int length) throws IOException;    public int readFully(long position,byte[] buffer) throws IOException;}
read()方法从指定position读取指定长度的字节放入buffer中指定偏移量offset,返回值是实际读取到的字节数:该值可能会小于指定的长度。readFully()方法会读取指定长度length的字节到buffer中或在只接受buffer字节数组的版本中读取buffer.length字节
2.2、写入数据

FileSystem类有一系列创建文件的方法,最简单就是给拟创建文件指定一个路径对象,然后返回一个用来写的输出流:

public FSDataOutputStream create(Path f) throws IOException;
注意:create()方法为需要写入的文件创建的父目录可能原先不存在,虽然很方便,但是有时并不希望这样,所以我们经常在先调用exists()检查父目录是否存在。

public FSDataOutputStream create(Path f,                                 Progressable progress)                          throws IOException
create()的另一个版本,需要重载接口Progressable中progress()方法,这样用硬就会告知数据写入数据节点的进度。

新建文件的另一种方法就是append()在一个已存在的文件后面追加:

public FSDataOutputStream append(Path f)                          throws IOException
FileSystem中的create()返回FSDataOutputStream,它也有类似有FSDataInputStream中查询当前位置方法getPos(),但是不允许定位,因为HDFS只允许对一个打开的文件顺序写入,或已有文件添加,不支持向除文件尾部的位置写入数据,所以定位没有意义。

2.3、目录
FIileSystem提供创建目录的方法:

public boolean mkdirs(Path f)               throws IOException
这个方法会创建所有必需的父目录,如果成功则返回true。常常并不需要确切地创建一个目录,因为create()写入时会生成所有的父目录。
2.4查询文件系统

(1)文件元数据:FileStatus;任何文件系统的一个重要特征是定位目录结构及检索存储文件和目录信息的能力。fileStatus类封装了文件系统中的文件和目录的元数据,包括文件长度、块大小、副本、修改时间、所有者以及许可信息。FileSystem提供getFileStatus()获取文件或目录状态的方法:

public abstract FileStatus getFileStatus(Path f)                                  throws IOException
如果文件或目录不存在,即会抛出FileNotFoundException异常,如果只对文件或目录是否存在感兴趣,exists()方法很方便。

(2)列出文件:查找文件或目录的信息很实用,但是我们有时还需要列出目录的内容,这就是listStatus()方法的功能:

public abstract FileStatus[] listStatus(Path f)                                 throws IOExceptionpublic FileStatus[] listStatus(Path f,                               PathFilter filter)                        throws IOExceptionpublic FileStatus[] listStatus(Path[] files)                        throws IOExceptionpublic FileStatus[] listStatus(Path[] files,                               PathFilter filter)                        throws IOException
传入参数为一个文件时,返回长度为1 的FileStatus对象的数组;当传入参数是一个目录时,返回的0个或多个FileStatus对象,代表该目录包含的所有文件和目录。

(3)文件格式:在一步操作中处理批量文件是很常见的,比如MapReduce处理一个月的日志文件。Hadoop有一个通配的操作,为通配提供了两个FileSystem的方法:

public FileStatus[] globStatus(Path pathPattern)                        throws IOExceptionpublic FileStatus[] globStatus(Path pathPattern,                               PathFilter filter)                        throws IOException
globStatus()返回其路径匹配所供格式的FileStatus对象数组,可选PathFilter进行进一步限制匹配。通配格式不总是能够精确地描述我们想要访问的文件的集合。比如使用通配格式排除一个特定的文件就不太可能。FileSystem中listStatus()和globStatus()方法都提供了可选的PathFilter对象,使我们能够通过编程方式控制匹配。
2.5删除数据

使用FileSystem中delete()方法可以永久性地删除文件或目录:

public abstract boolean delete(Path f,                               boolean recursive)                        throws IOException
如果传入的f是一个文件或一个空目录,那么recursive的值就会被忽略。只有在recursive为true时,一个非空目录及其内容才会被删除(否则抛出IOException异常)。
3、数据流
3.1、文件读取剖析


客户端通过调用FileSystem对象的open()来读取希望打开的文件(步骤1),对于HDFS来说这是一个分布式文件系统的实例,HDFS通过RPC来调用名称节点,已确定文件开头部分块的位置(步骤2),对于每个块名称节点返回每个副本的数据节点的地址。此外,这些数据节点还会根据它们与客户端的距离排序。接着,客户端通过输入流FSDataInputStream调用read()(步骤3),通过在数据流中反复调用read(),数据从数据节点返回客户端(步骤4),当到达块的末端位置时,FSDataInputStream会关闭与数据节点的联系,为下一个块寻找最佳的数据节点(步骤5)。一旦客户端完成读取,就对文件系统数据输入流调用close()(步骤6)。
3.2、文件写入剖析


客户端通过在HDFS调用create()方法创建文件(步骤1),利用RPC调用名称节点,在文件系统的命名空间创建一个新文件,没有块与之相联系(步骤2)。在客户端写入数据时(步骤3),FSDataOutputStream将数据流分成一个个的包,写入内部队列,称为数据队列。数据队列随数据流流动,数据流的责任就是根据合适的数据节点的列表来要求这些节点为为副本分配新的块,这些数据节点的列表形成一个管线(步骤4)。FSDataOutputStream内部也有一个包队列等待数据节点收到包确认,一个包只有在管线中所有节点确认后才被移出确认队列(步骤5)。客户端完成数据写入后,就会在流中调用close()(步骤6)。
4、Hadoop的归档文件

每个文件以块方式存储,块的元数据存储在名称节点的内存里,此时存储一些小的文件,HDFS会较低效。大量的小文件会消耗名称节点中大部分内存。Hadoop Archives或HAR文件是一个更高效的将文件放入到HDFS块中的存档设备,减少名称节点内存使用的同时,仍然允许对文件的透明访问。Hadoop Archives通过使用archive工具根据一个文件集合创建而来。这些工具运行一个MapReduce作业来并行处理输入文件:

hadoop archive -archiveName files.har /my/files/ /my
第一个选项是归档文件名称,这里是files.har,接下来是归档源树,再是目的树。

不足之处:创建一个归档文件会创建一个原始文件的副本,因此需要与归档文件同样大小的磁盘空间。而且一旦创建,Archives便不可改变。增加或者移除文件都必须重新创建归档文件。


1 0
原创粉丝点击