《Hadoop权威指南》学习笔记(3)——Hadoop分布式文件系统

Hadoop分布式文件系统,即HDFS(Hadoop Distributed FileSystem)。

HDFS的设计

1、存储超大文件(指大小为几百MB、几百GB甚至几百TB大小的文件)
2、流式数据访问:一次写入、多次读取,这是一种比较高效的访问方式。这是因为数据分析往往是先读入一个数据集,然后长期在其上进行各种分析,所以读取整个数据集的时间延迟更为重要。
3、时间延迟较高,不适合要求低时间延迟数据访问的应用,因为它是为高数据吞吐量应用优化的。
4、HDFS中的文件只支持单个写入者,并且写操作总以“只添加”方式在文件末尾写数据。不支持多用户写作和任意修改文件。
5、设计运行在商用硬件(普通硬件)的集群上。

HDFS的特点

高吞吐量,高容错性、线性扩展
(这些特点在了解HDFS的结构后能够更方便的理解)

HDFS架构

在介绍架构前,首先要明确一些概念:
数据块:正如磁盘划分为数据块一样,HDFS中也有数据块的概念,但是远比磁盘中的块要大,默认为128MB。相应的,磁盘存储文件时会占据整个块,但是HDFS中不会,1MB的文件只会占用1MB的空间,而不会占用整个128MB大小的块。
Namenode:管理文件系统的命名空间,维护文件系统树以及整棵树内所有的文件和目录。这些文件以两个文件形式永久保存在本地磁盘上:命名空间镜像文件和编辑日志文件。
Datanode:文件系统的工作节点,根据需要存储并检索数据块,并定期向namenode发送所存储的块的列表。
Secondary Namenode:即辅助namenode,用来实现HDFS的容错机制,在namenode发生故障时启用,避免文件丢失。它的主要作用是定期合并编辑日志和命名空间镜像,以防止编辑日志过大。也相当于是保存namenode的副本。
HDFS的大致结构如图:
在这里插入图片描述
至于具体每一个步骤如何进行,会在后面的数据流部分介绍。

HDFS的高可用性

首先要说明一下联邦HDFS,联邦HDFS就是说允许有多个namenode以实现扩展,每个namenode管理文件系统命名空间中的一部分。在这种情况下,没个namenode维护一个命名空间卷(namespace volume),由命名空间的元数据和一个数据块池组成,数据块池包含该命名空间下文件的所有数据快。namenode之间不互相通信,一个namenode的失效不会影响其他namenode所维护命名空间的可用性。
因为namenode的失效会导致HDFS无法使用。因此要想保证HDFS的高可用性,就要尽快恢复namenode来响应服务。而新的namenode需要满足几点要求才能继续响应服务:①将命名空间的映像导入内存中 ②重演编辑日志 ③接收到足够多的来自datanode的数据块报告并退出安全模式。然而这一步骤所需时间较强,因此为了实现高可用性,HDFS配置了活动-备用(active-standby)namenode。这样,在活动namenode失效时,备用namnode就会接管服务并且不会产生明显中断。
为了实现这种高可用性,需要对架构进行以下修改:
①当备用namenode接管工作后,通读共享编辑日志至末尾,以实现与活动namenode的状态同步,并继续读取由活动namenode写入的新条目。
②datanode要同时向两个namenode发送数据块处理报告。
③客户端要用特定机制处理namenode的失效问题,这一机制对用户透明。
④备用namenode包含了辅助namenode的角色,为,活动namenode命名空间设置周期性检查点。
高可用性共享存储有两种方案:NFS过滤器或群体日志管理器(QJM)。其中QJM是一个周昂用的HDFS实现,应用于大多数HDFS部署中。
这样,在活动namenode失效后,只需要几十秒的时间,备用namenode就能够接管服务。

HDFS的接口

命令行接口

HDFS既然是一个分布式文件系统,自然是要在分布式系统上运行,但因为条件限制,所以在个人主机上,我们可以配置伪分布式模式的Hadoop。
关于如何配置伪分布式模式的Hadoop,在这篇文章中有较为清楚的教程与介绍。(要注意一下,其中配置core-site.xml时,端口号最好使用8020,用9000的话之后运行程序会有问题。如果已经按9000配置了,就需要关闭hdfs,删除数据,然后更改core-site.xml文件并重启。)
然后就是关于通过命令行在本地对HDFS进行操作,Hadoop HDFS的一系列命令在这篇文章中介绍的很清楚。
需要注意的是,当使用./bin/hdfs dfs类命令时,需要在hadoop文件下进行;而使用hadoop -fs类命令时,可以在任意文件目录下进行,但前提是需要配置好环境变量。
此外,书中还接料一个将文件传入HDFS并再传回本地的例子,为了确认该文件与原文件是否完全一致,因此使用了md5sum命令来判断MD5键值是否相同,相同则代表文件未发生任何改变,保持完整。

Java接口

Hadoop中有一个抽象的文件系统概念,HDFS只是其中的一个实现,相应的,hdfs.DistributedFileSystem是抽象类org.apache.hadoop.fs.FileSystem的一个实现,为了使我们写出的程序可以在不同的文件系统间可移植,因此,在程序中还是应当使用FileStystem抽象类。
因此,Java接口主要就是利用FileSystem API进行读写数据等一系列操作。

1、读取数据

Hadoop文件系统中通过Hadoop Path对象来代表文件,即可将路径视为一个Hadoop文件系统URI,如hdfs://localhost/user/tom/quangle.txt

public static FileSystem get(Configuration conf) throws IOException
public static FileSystem get(URI uri, Configuration conf) throws IOException
public static FileSystem get(URI uri, Configuration conf, String user) throws IOException

这三个方法时用来检索我们要使用地的文件系统实例,其中Configuration封装了客户端/服务器的设置,更改配置文件如core-site.xml就是相当于在设置这个对象。

public static LocalFileSystem getLocal(Configuration conf) throws IOException

当想要获取本地文件系统的运行系统实例时,也可以使用更方便的getLocal()方法。
在获得文件系统FileSystem实例后,我们便可以读入文件了

public FSDataInputStream open(Path f) throws IOException
public abstract FSDataInputStream open(Path f, int bufferSzie) throws IOException

我们可以注意到open()方法的返回值类型时FSDataInputStream对象。这个类是继承了java.io.DataInputStream的一个特殊类,它实现了Seekable接口,支持随机访问,即可以从流的任意位置读取数据(利用seek()方法)。同时,它还实现了PositionedReadable接口,可以从一个指定偏移量处读取文件的一部分。
需要注意的是,seek()方法的开销较高,要慎重使用。

2、写入数据

FileSystem类有一系列新建文件的方法:

public FSDataOutputStream create(Path f) throws IOException
public FSDataOutputStream append(Path f) throws IOException

其中create()是最简单的方法,它有多个重载版本,允许指定是否覆盖现有文件、文件权限、文件备份数量以及文件块大小等。
而append()方法,顾名思义,是用来在现有文件末尾追加数据的,它也还有一些重载版本。
此外还有一个重载方法Progressable用于传递回调接口,可以将数据写入datatnode的进度通知给应用:

package org.apache.hadoop.util;
public interface Progressable{
	public void progress();
} 

利用Progressable来展示进度对于MapReduce是比较重要的。
对于新建文件的方法,我们可以看到返回类型是与FSDataInputStream类对应的FSDataOutputStream类,它们有类似的操作,但不同的是,FSDataOutputStream类不允许在文件中定位。

3、创建目录
public boolean mkdirs(Path f) throws IOException

该方法可以一次应新建所有必要但还没有的父目录,若目录均创建成功,则返回true
但通常情况行,因为调用create()方法写入文件时会自动创建父目录,所以不需要显式创建目录。

4、查询文件系统

4.1. 文件元数据:FileStatus
文件系统的一个重要特征是提供其目录结构浏览和检索它所在文件和目录相关信息的功能。
FileStatus类封装了文件系统中文件和目录的元数据,包括文件长度、块大小、复本、修改时间、所有者以及权限信息,
FileSystem的getFileStatus()方法用于获取文件或目录的FIleStatus对象。
若文件或目录不存在,会抛出FileNotFoundException异常,因此若只是像检查文件或目录是否存在,调用exists()方法即可。
4.2. 列出文件
FileSystem的listStatus()方法可以用来列出文件目录中的内容:

public FileStatus[] listStatus(Path f) throws IOException
public FileStatus[] listStatus(Path f, PathFilter filter) throws IOException
public FileStatus[] listStatus(Path[] files) throws IOException
public FileStatus[] listStatus(Path[] files, PathFilter filter) throws IOException

Hadoop的FIleUtil类中的stat2Paths()方法可以将上面返回的FileStatus对象数组转换为一个Path对象数组。
4.3. 文件模式
在单个操作中处理一批文件是很常见的,因此,在一个表达式中使用通配符来匹配多个文件显然是更加方便的。
Hadoop为执行通配提供了两个FileSystem方法:

public FileStatus[] globStatus(Path pathPattern) throws IOException
public FileStatus[] globStatus(Path pathPattern, PathFilter filter) throws IOException

返回值是路径格式与指定模式匹配的所有FileStatus对象组成的数组,并按路径排序,PathFilter用于对匹配结果进行限制。(Path是通配符模式,而PathFilter是正则表达式)

4.4. PathFilter对象
PathFilter再FileSystem中的接口实现如下所示:

package org.apache.hadoop.fs;
public interface PathFilter{
	public void accept(Path path);
} 

其中accept()方法用来实现对输入的正则表达式如何处理,如排除匹配该正则表达式的路径或留下匹配该正则表达式的路径等。

5、删除数据

FileSystem的delete()方法可以永久性删除文件或目录:

public boolean delete(Path f, boolean recursive) throws IOException

若 f 是一个文件或空目录,那么recursive的值不会影响;若 f 为非空目录,则只有在recursive为true时,非空目录及其内容才会被删除。

数据流

在对HDFS进行文件读取、文件写入时,客户端、HDFS、datanode以及namenode之间的数据流可以用两张图来表示。
客户端读取HDFS中的数据
1、客户端通过调用 FileSystem 对象的open()方法来打开希望读取的文件,对于 HDFS 来说,这个对象是DistributedFileSystem的一个实例。
2、DistributedFileSystem 通过使用 RPC(远程过程调用)来调用 NameNode 以确定文件起始块的位置,对每一个块,返回存有该副本的所有DataNode地址,这些DataNode根据它们与客户端的距离来排序(距离根据集群的网络拓扑决定的)。
3、DistributedFileSystem类会返回一个 FSDataInputStream 对象,该对象会被封装成DFSInputStream 对象,DFSInputStream 可以方便的管理 DataNode 和 NameNode 数据流,客户端对这个输入流调用read()方法。
4、存储着文件起始块的 DataNode 地址的 DFSInputStream 随即连接距离最近的 DataNode,通过对数据流反复调用read()方法,可以将数据从 DataNode 传输到客户端。
5、到达块的末端时,DFSInputStream 会关闭与该 DataNode 的连接,然后寻找下一个块的最佳 DataNode,这些操作对客户端来说是透明的,从客户端的角度来看是一直在读一个持续不断的流。
6、一旦客户端完成读取,就对 FSDataInputStream 调用close()方法关闭文件读取。
客户端将数据写入HDFS
1、客户端通过对 DistributedFileSystem 对象调用create()方法创建新文件。
2、DistributedFileSystem 通过 RPC 调用 NameNode 在系统的命名空间中新建一个没有相应数据块的新文件,创建前 NameNode 会做各种校验,比如文件是否存在、客户端有无权限去创建等。如果校验通过,NameNode 会为创建新文件记录一条记录,否则就会抛出 IO 异常。
3、DistributedFileSystem向客户端返回一个 FSDataOutputStream 的对象,和读文件的时候相似,FSDataOutputStream 被封装成 DFSOutputStream,DFSOutputStream 负责处理 NameNode 和 Datanode 之间的通信。客户端开始写数据到 DFSOutputStream,DFSOutputStream 会把数据切成一个个小的数据包,并写入内部队列称为“数据队列”(Data Queue)。
4、DataStreamer 处理接受数据队列,它的责任是问询 NameNode 这个新的数据的复本最适合存储在哪几个 DataNode 里,比如重复数是 3,那么就找到 3 个最适合的 DataNode,把他们排成一个管线 (pipeline) 。DataStreamer 把数据包按队列传输到管道的第一个 Datanode 中,第一个 DataNode 又把数据包发送给第二个 DataNode 中,以此类推。
5、DFSOutputStream 还有一个队列叫“确认队列” (Ack Quene) ,也维护着一个内部数据包队列来等待 DataNode 的收到响应,当管线中的所有 DataNode 都表示已经收到的时候,这时确认队列 才会把对应的数据包移除掉。
6、客户端完成写数据后调用close()方法关闭写入流。
7、DataStreamer 把剩余的包都写入DataNode管线并在联系到NameNode告知其文件写入完成之前,等待确认。

一致模型

文件系统的一致模型描述了文件读/写的可见性。
新建一个文件后,它能在文件系统的命名空间立即可见。但是,对于大部分文件系统,写入的内容并不能保证立即可见,即使数据流已经刷新并存储。即当前正在写入的块对其他 reader 不可见。
HDFS为了改变这一点,即为了保证文件中到目前为止写入的所有数据均到达所有 datanode 的写入管道并对所有 reader 均可见,提供了一种强行将所有缓存时刷新到 datanode 中的手段,即对 FSDataOutputStream 调用hflush()方法。
但是,hflush()不保证 datanode 已将数据写到磁盘上,仅确保数据在 datanode 的内存中。因此要确保数据写入到磁盘上,可以使用hsync()
在程序过程中,需要在适当位置调用hflush()方法,以避免系统发生故障时丢失数据块。

学习实践HDFS中遇到的问题

如果在运行Java编译产生的.class文件时遇到hadoop相关的库未导入的问题,需要到hadoop的目录中找到相应的jar配置文件,或者可以直接使用对应的整个目录,添加到配置文件中。
java文件编译得到的.class文件不能直接用hadoop运行,应该先将其打包为jar,然后再运行相关文件。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值