第三章之Hadoop分布式文件系统
管理网络中跨多台计算机存储的文件系统称为分布式文件系统。Hadoop有一个称为HDFS的分布式系统称为Hadoop Distributed Filesystem
HDFS设计
HDFS以流式数据访问模式来存储超大文件,运行于商用硬件集群上。
流式数据访问:HDFS的构建思路:一次写入,多次读取的高效的访问模式。数据集通常是由数据源生成或者从数据源复制而来,接着长时间在此数据集上进行各类分析,每次分析都涉及该数据集的大部分或者是全部,因此读取整个数据集的时间延迟比读取第一条记录的时间延迟更为重要。
商用硬件:节点故障的几率还是非常高的,HDFS遇到上述故障的时候,被设计成能够继续运行且不让用户察觉到明显的中断。
低时间延迟的数据访问:要求低时间延迟数据访问的应用(几十毫秒),不适合在HDFS上运行,HDFS是为了高数据吞吐量应用优化的,这可能会以高时间延迟为代价。对于低延迟的需求,HBase是更好的选择
大量的小文件:由于namenode讲文件系统的元数据存储在内存中,因此该文件系统能存储的文件总数受限于namenode的内存总量。
多用户写入,任意修改文件:HDFS中的文件可能只有一个writer,而且写操作总是将数据添加在文件的末尾。它不支持具有多个写入者的操作,也不支持在文件的任意位置进行修改,可能以后会支持这些操作,但是他们相对比较低效。
HDFS的概念
数据块:磁盘有数据块一说,构建于磁盘上的文件系统通过磁盘快来管理该文件系统中的块,该文件系统块的大小可以是磁盘快的整数倍,磁盘块一般为512字节。HDFS的块大的多,默认为64M,很多都是128M。与单一磁盘上的文件系统相似,HDFS上的文件也被划分为块大小的多个分块(chunk),作为独立的存储单元,与其他文件系统不同的是,HDFS中小于一个块大小的文件不会占据整个快的空间。
对于分布式文件系统中的块进行抽象会带来很多好处,最大的1:文件的大小可以大于网络中任意一个磁盘的容量,文件的所有块并不需要存储在同一个磁盘上,因此他们可以利用集群上的任意一个磁盘进行存储。2:使用块抽象而非整个文件作为存储单元,大大简化了存储子系统的设计,简化对于故障种类繁多的分布式系统来说尤为重要。简化存储管理。消除了对元数据的顾虑(块只是存储数据的一部分,而文件的元数据,如权限信息,并不需要与块一同存储,这样一来,其他系统就可以单独的管理这些元数据) 块也非常适合用户数据备份进而提供数据容错能力和可用性。
namenode和datanode
HDFS集群有两类节点,并以管理者-工作者模式运行,即:一个namenode(管理者)和多个datanode(工作者)。
namenode管理文件系统的命名空间,他维护着文件系统树以及整棵树内所有的文件和目录,这些信息以两个文件形式永久保存在本地磁盘上:命名空间镜像文件和编辑日志文件。namenode也记录着每个文件中各个块所在的数据节点信息,但他并不永久保存块的位置信息,因为这些信息会在系统启动的时候由数据节点重新建立。
客户端 代表 通过与namenode和datanode交互来访问整个文件系统。客户端提供一个类似与POSIX的文件系统接口,因此用户在编程的时候无需要知道namenode和datanode也可以实现其功能。
datanode是文件系统的工作节点,他们根据需要存储并检索数据块,并定期向namenode发送他们所存储的块的列表。
没有namenode,系统无法使用,so容错很重要
一种是备份那些文件系统元数据持久状态的文件。hadoop可以通过配置使namenode在多个文件系统上保存元数据的持久状态,这些写操作是实时同步的,是原子操作。一般的配置是,将持久状态写入本地磁盘的同时,写入一个远程挂载的网络文件系统(NFS)。
另一种:运行辅助namenode,但他不能用作namenode,这个辅助作用是定期编辑日志合并命名空间镜像,以防止编辑日志过大。总会滞后些,
HDFS中的文件访问权限
一共提供三类权限模式:只读权限,写入权限,可执行权限。
每个文件和目录都有所属用户(owner),所属组列(group)以及模式(mode)。这个模式是由所属用户的权限,组内成员的权限以及其他用户的权限组成的。
这里有一个超级用户的概念,超级用户是namenode进程的标识,对于超级用户,系统不会执行任何权限检查。
Thrift
因为Hadoop文件系统的接口是通过java API提供的,所以非java程序访问Hadoop文件系统会比较麻烦。
thriftfs包装了服务。弥补不组。不过需要运行提供Thrift服务的java服务器,并以代理的方式访问Hadoop文件系统。
HDFS有两种特定的接口
HTTP定义了一个以HTTP方式检索目录列表和数据的只读接口。
FTP该接口允许使用FTP协议与HDFS进行交互。
从HadoopURL中读取数据
要从Hadoop文件系统中读取文件,最简单的方式是使用java.net.URL对象打开数据流,进而从中读取数据
InputStream in=null;
try
{
in=new URL("hdfs://host/path").openStream();
//process in
} finally
{
IOUTils.clouseStream(in);
}
通过FileSystem API 读取数据
有时候无法在应用中设置URLStreamHandlerFactory实例。需要使用FileSystemAPI来打开一个文件的输入流。
Hadoop文件系统中通过Hadoop Path对象来代表文件(而非java.io.file对象)你可以将一条路径视为一个Hadoop文件系统URI。
查询文件系统
文件元数据:FileStatus
FileStatus类封装了文件系统中的文件和目录的元数据,包括文件长度,块大小,备份,修改时间,所有者以及权限信息。
数据流
文件读取剖析:
为了了解客户端以及与之交互的HDFS,namenode,datanode之间的数据流是什么样的。如图顺序
(1)open()方法来打开希望读取的文件
(2)DistributedFileSystem通过使用RPC来调用namenode,以确定文件起始块的位置,对于每一个块,namenode返回存有该块复本的datanode地址。这些datanode根据他们与客户端的距离来排序,如果客户端本身就是一个datanode,并高村有相应数据块的一个复本的时候,该节点讲从本地datanode中读取数据
(3)客户端对这个输入流调用read()方法。存储文件七十块的datanode地址的DFSInputStream随即连接最近的datanode,
(4)通过对数据流反复调用read()方法,可以将数据从datanode传输到客户端。,
(5)到达块的末端的时候,DFSInputStream会关闭与该datanode连接,寻找下一个块的最佳datanode。
(6)客户端从流中读取数据时候,块是按照打开DFSInputStream与datanode新建连接的顺序读取的,他也需要询问namenode来检索下一批所需要的datanode'的位置,一旦读取完整,就对FSDataInputStream调用close方法。
文件写入剖析
(1)客户端通过对DistributiedFileSystem 对象调用create()函数来创建文件。
(2)DistributedFileSystem 对namenode创建一个RPC调用,在文件系统的命名空间中创建一个新文件,此时该文件中还没有相应的数据块,namenode执行各种不同的检查以确保这个文件不存在,并且客户端有创建该文件的权限。,检查全部通过,namenode就会为创建文件记录一条记录,否则,抛出失败异常。
(3)在客户端写入数据,DFSOoutputStream将他分成一个个的数据包,并写入内部队列,称为“数据队列”,DataStreamer处理出具队列,他的责任是根据datanode列表来要求namenode分配适合的新块来存储数据备份。
(5)DFSOutputStream 也维护着一个内部数据包队列来等待datanode的收到缺人回执,称为“确认队列”,当收到管道中所有datanode确认信息后,该数据包才会从确认队列中删除。
写入期间,datanode发生故障,则执行下面操作,首先关闭管线,确认队列中的任何数据包都添加数据队列的最前端,以保证故障节点下游的datanode不会漏掉任何一个数据包。为存储在另一正常datanode的当前数据块指定一个新的标志,并讲该标志关送给namenode,以便组长datanode在回复后可以删除存储的部分数据块。
(6)写完数据,调用close()方法,该操作将剩余的所有数据包写入datanode管线中,并在联系namenode且发送文件写入完成信号之前,等待确认
(7)namenode已经知道文件由哪些块组成,所以在他的返回成功之前只需要等待数据块进行最小量的复制。
一致模型
文件系统的一致模型(coherency model)描述了对文件读/写的数据可见性。
创建一个文件以后,希望他能在文件系统的命名空间立即可见,如下所示
Path p=new Path("P"); Fs.Create(p); assertThat(fs.exists(p),is(true));
但是,写入文件的内容并不保证能立即可见,即使数据流已经刷新并存储,所以文件长度显示为0,当写入数据超过一个块以后,新的热ader就能看见第一个块,之后的块也不例外。总之其他reader无看见当前正在写入的块。
HDFS提供了一个方法来强制所有的缓存与数据节点同步,即对FSDataOutputStream调用sycn()方法:该调用将提交一个文件描述符的缓冲数据。当sync()方法返回成功后,对所有新的reader而言,HDFS能保证文件中到目前为止写入的数据均可见且一致 。
如果不嗲用sync()方法,就需要准备好客户端或者系统发生故障的时候可能会丢失一个数据块,这对许多系统来说是不可以接受的恶,所以你需要在适当的地方调用sync()方法。尽管sync()操作被设计成尽量减少HDFS负载,但是它有许多额外的开销,所以在数据鲁棒性和吞吐量之间就会有取舍。
通过Distcp并行复制
前面介绍单线程访问的HDFS访问模型,例如通过制定文件通配符,可以对一组文件进行处理,但是为了提高性能,需要写一个程序来并行处理这些文件。
Hadoop有一个有用的distcp分布式复制程序,该程序可以从Hadoop文件系统中复制大量数据,也可以将大量的数据复制到Hadoop中。
distcp典型应用是在在两个HDFS集群之间传输数据,如果两个集群运行相同版本的Hadoop,就非常适合使用HDFS方案。
distcp是作为一个MapReduce作业来实现的,该复制作业是通过集群中并行运行的map来完成,这里没有reducer。每个文件通过map进行复制,并且distcp试图为每一个map分配大致相等的数据来执行,这一步通过将文件划分为大致相等的块来实现。
为了保持HDFS集群的均衡可以使用均衡器这个平衡工具(balancer)。
Hadoop存档
每个文件按照块进行存储,每个块的原数据存储在namenode的内存中,因此Hadoop存储小文件会非常低效,因为大量的小文件会耗尽namenode中大部分内存。Hadoop存档文件或者是HAR文件,是一个高效的文件存档工具,它将文件存入HDFS块,再减少namenode内存使用的同时,还能允许对文件进行透明的访问。具体来说,Hadoop存档文件可以用作MapReduce的输入。