3.1 认识HDFS
Hadoop海量数据处理:技术详解与项目实战
HDFS的设计理念源于非常朴素的思想:当数据集的大小超过单台计算机的存储能力时,就有必要将其进行分区(partition)并存储到若干台单独的计算机上,而管理网络中跨多台计算机存储的文件系统称为分布式文件系统(distribute filesystem)。该系统架构于网络之上,势必会引入网络编程的复杂性,因此分布式文件系统比普通文件系统更为复杂,例如,使文件系统能够容忍节点故障且不丢失任何数据,就是一个极大的挑战。通过本章的介绍,我们可以发现HDFS很好地完成了这个挑战。
准确地说,Hadoop有一个抽象的文件系统概念,HDFS只是其中的一个实现。Hadoop文件系统接口由Java抽象类org.apache.hadoop.fs.FileSystem类定义,该类同时还继承了org.apache.hadoop.conf并且实现了Java的java.io.Closeable接口。表3-1所示是抽象类FileSystem的几个具体实现。
基于Amazon S3的文件系统,解决了S3的5 GB文件大小的限制
Hadoop提供了许多文件系统的接口,用户可以选取合适的URI方案来实现对特定的文件系统的交互。例如,如果想访问本地文件系统,执行以下shell命令即可:
hadoop dfs -ls file:/// (最后一个/表示本地文件系统的根目录)
执行完成后,屏幕会打印出以下信息:
dr-xr-xr-x - root root 12288 2014-04-07 09:33 /lib64
drwxr-xr-x - root root 4096 2014-02-22 06:15 /media
drwxr-xr-x - root root 0 2014-05-02 10:03 /net
drwxr-xr-x - root root 4096 2011-09-23 07:50 /srv
drwx------ - root root 16384 2014-01-27 05:55 /lost+found
drwx------ - root root 4096 2014-01-27 06:20 /.dbus
dr-xr-xr-x - root root 4096 2014-04-07 09:34 /bin
-rw-r--r-- 1 root root 0 2014-05-02 17:01 /.autofsck
drwxr-xr-x - root root 4096 2014-01-27 05:56 /usr
dr-xr-xr-x - root root 12288 2014-04-07 09:34 /sbin
…
如果想访问HDFS文件系统,执行以下命令即可:
hadoop dfs -ls hdfs:///
执行完成后,屏幕会打印出以下信息:
Found 3 items
drwxr-xr-x - hadoop supergroup 0 2014-04-02 11:52 /home
drwxr-xr-x - hadoop supergroup 0 2014-04-06 12:13 /tmp
drwxr-xr-x - hadoop supergroup 0 2014-04-06 12:10 /user
3.1.1 HDFS的设计理念
作为Hadoop生态圈的基础,HDFS非常适合运行在廉价硬件集群之上,以流式数据访问模式来存储超大文件。简单的一句话,已经勾勒出HDFS的特点。
(1)适合存储超大文件:存储在HDFS的文件大多在GB甚至TB级别,目前阿里巴巴的集群存储的数据已经达到了60 PB。
(2)运行于廉价硬件之上:HDFS在设计的时候,就已经认为在集群规模足够大的时候,节点故障并不是小概率事件,而可以认为是一种常态。例如,一个节点故障的概率如果是千分之一,那么当集群规模是1000台时,正常情况每天都会有节点故障。当节点发生故障时,HDFS能够继续运行并且不让用户察觉到明显的中断,所以HDFS并不需要运行在高可靠且昂贵的服务器上,普通的PC Server即可。
(3)流式数据访问:HDFS认为,一次写入,多次读取是最高效的访问模式。HDFS存储的数据集作为Hadoop的分析对象,在数据集生成后,会长时间在此数据集上进行各种分析。每次分析都将涉及该数据集的大部分数据甚至全部数据,因此读取整个数据集的时间延迟比读取第一条记录的时间延迟更重要。
除了上面3点,HDFS也有一些短板。
(1)实时的数据访问弱:如果应用要求数据访问的时间在秒或是毫秒级别,那么HDFS是做不到的。由于HDFS针对高数据吞吐量做了优化,因而牺牲了读取数据的速度,对于响应时间是秒或是毫秒的数据访问,可以考虑使用HBase。
(2)大量的小文件:当Hadoop启动时,NameNode会将所有元数据读到内存,以此构建目录树。一般来说,一个HDFS上的文件、目录和数据块的存储信息大约在150字节左右,那么可以推算出,如果NameNode的内存为16 GB的话,大概只能存放480万个文件,对于一个超大规模的集群,这个数字很快就可以达到。
(3)多用户写入,任意修改文件:HDFS中的文件只能有一个写入者,并且写数据操作总是在文件末。它不支持多个写入者,也不支持在数据写入后,在文件的任意位置进行修改。事实上,如果不将hdfs-site.xml中的dfs.support.append设置为true的话,HDFS也不支持对文件进行追加操作。
3.1.2 HDFS的架构
在前面我们已经大致了解了HDFS的架构,下面将会详细地介绍架构中的每一部分。一个完整的HDFS运行在一些节点之上,这些节点运行着不同类型的守护进程,如NameNode、DataNode、SecondaryNameNode,不同类型的节点相互配合,相互协作,在集群中扮演了不同的角色,一起构成了HDFS。
如图3-1所示,一个典型的HDFS集群中,有一个NameNode,一个SecondaryNode和至少一个DataNode,而HDFS客户端数量并没有限制。所有的数据均存放在运行DataNode进程的节点的块(block)里。
1.块
每个磁盘都有默认的数据块大小,这是磁盘进行数据读/写的最小单位,而文件系统也有文件块的概念,如ext3、ext2等。文件系统的块大小只能是磁盘块大小的整数倍,磁盘块大小一般是512字节,文件系统块大小一般为几千字节,如ext3的文件块大小为4 096字节,Windows的文件块大小为4 096字节。用户在使用文件系统对文件进行读取或写入时,完全不需要知道块的细节,这些对于用户都是透明的。
HDFS同样也有块(block)的概念,但是HDFS的块比一般文件系统的块大得多,默认为64 MB,并且可以随着实际需要而变化,配置项为hdfs-site.xml文件中的dfs.block.size项。与单一文件系统相似,HDFS上的文件也被划分为块大小的多个分块,它是HDFS存储处理的最小单元。
某个文件data.txt,大小为150 MB,如果此时HDFS的块大小没有经过配置,默认为64 MB,那么该文件实际在HDFS中存储的情况如图3-2所示。
圆形为保存该文件的第一个块,大小为64 MB,方形为保存文件的第二个块,大小为64 MB,五边形为保存文件的第三个块,大小为22 MB,这里特别指出的,与其他文件系统不同的是,HDFS小于一个块大小的文件不会占据整个块的空间,所以第三块的大小为22 MB而不是64 MB。
HDFS中的块如此之大的原因是为了最小化寻址开销。如果块设置的足够大,从磁盘传输数据的时间可以明显大于定位这个块开始位置所需的时间。这样,传输一个由多个块组成的文件的时间取决于磁盘传输的效率。得益于磁盘传输速率的提升,块的大小可以被设为128 MB甚至更大。
在hdfs-site.xml文件中,还有一项配置为dfs.relication,该项配置为每个HDFS的块在Hadoop集群中保存的份数,值越高,冗余性越好,占用存储也越多,默认为3,即有2份冗余,如果设置为2,那么该文件在HDFS中存储的情况如图3-3所示。
使用块的好处是非常明显的。
(1)可以保存比存储节点单一磁盘大的文件:块的设计实际上就是对文件进行分片,分片可以保存在集群的任意节点,从而使文件存储跨越了磁盘甚至机器的限制,如data.txt文件被切分为3个块,并存放在3个DataNode之中。
(2)简化存储子系统:将存储子系统控制单元设置为块,可简化存储管理,并且也实现了元数据和数据的分开管理和存储。
(3)容错性高:这是块非常重要的一点,如果将dfs.relication设置为2,如图3-2,那么任意一个块损坏,都不会影响数据的完整性,用户在读取文件时,并不会察觉到异常。之后集群会将损坏的块的副本从其他候选节点复制到集群中能正常工作的节点,从而使副本数回到配置的水平。
2.NameNode和SecondaryNameNode
NameNode也被称为名字节点,是HDFS的主从(master/slave)架构的主角色的扮演者。NameNode是HDFS的大脑,它维护着整个文件系统的目录树,以及目录树里所有的文件和目录,这些信息以两种文件存储在本地文件中:一种是命名空间镜像(File System Image,FSImage,也称为文件系统镜像),即HDFS元数据的完整快照,每次NameNode启动的时候,默认会加载最新的命名空间镜像,另一种是命名空间镜像的编辑日志(Edit Log)。
SecondaryNameNode,也被称为第二名字节点,是用于定期合并命名空间镜像和命名空间镜像的编辑日志的辅助守护进程。每个HDFS集群都有一个SecondaryNameNode,在生产环境下,一般SecondaryNameNode也会单独运行在一台服务器上。
FSImage文件其实是文件系统元数据的一个永久性检查点,但并非每一个写操作都会更新这个文件,因为FSImage是一个大型文件,如果频繁地执行写操作,会使系统运行极为缓慢。解决方案是NameNode只将改动内容预写日志(WAL),即写入命名空间镜像的编辑日志(Edit Log)。随着时间的推移,Edit Log会变得越来越大,那么一旦发生故障,将会花费非常多的时间来回滚操作,所以就像传统的关系型数据库一样,需要定期地合并FSImage和Edit Log日志。如果由NameNode来做合并的操作,那么NameNode在为集群提供服务时可能无法提供足够的资源,为了彻底解决这一问题,SecondaryNameNode应运而生。NameNode和SecondaryNameNode交互如图3-4所示。
(1)SecondaryNameNode引导NameNode滚动更新Edit Log文件,并开始将新的内容写入Edit Log.new。
(2)SecondaryNameNode将NameNode的FSImage和Edit Log文件复制到本地的检查点目录。
(3)SecondaryNameNode载入FSImage文件,回放Edit Log,将其合并到FSImage,将新的FSImage文件压缩后写入磁盘。
(4)SecondaryNameNode将新的FSImage文件送回NameNode,NameNode在接受新的FSImage后,直接加载和应用该文件。
(5)NameNode将Edit Log.new更名为Edit Log。
默认情况下,该过程每小时发生一次,或者当NameNode的Edit Log文件达到默认的64 MB也会被触发。
从名称上来看,初学者会以为当NameNode出现故障时,SecondaryNameNode会自动成为新的NameNode,也就是NameNode的“热备”。通过上面的介绍,我们清楚地认识到这是错误的。
3.DataNode
DataNode被称为数据节点,它是HDFS的主从架构的从角色的扮演者,它在NameNode的指导下完成I/O任务。如前文所述,存放在HDFS的文件都是由HDFS的块组成,所有的块都存放于DataNode节点。实际上,对于DataNode所在的节点来说,块就是一个普通的文件,我们可以去DataNode存放块的目录下观察(默认是$(dfs.data.dir)/current),块的文件名为blk_blkID。
DataNode会不断地向NameNode报告。初始化时,每个DataNode将当前存储的块告知NameNode,在集群正常工作时,DataNode仍然会不断地更新NameNode,为之提供本地修改的相关信息,同时接受来自NameNode的指令,创建、移动或者删除本地磁盘上的数据块。
4.HDFS客户端
HDFS客户端是指用户和HDFS交互的手段,HDFS提供了非常多的客户端,包括命令行接口、Java API、Thrift接口、C语言库、用户空间文件系统,本章将在3.3节详细介绍如何与HDFS进行交互。
3.1.3 HDFS容错
本节将回答本章开头的问题:如何使文件系统能够容忍节点故障且不丢失任何数据,也就是HDFS的容错机制。
1.心跳机制
在NameNode和DataNode之间维持心跳检测,当由于网络故障之类的原因,导致DataNode发出的心跳包没有被NameNode正常收到的时候,NameNode就不会将任何新的I/O操作派发给那个DataNode,该DataNode上的数据被认为是无效的,因此NameNode会检测是否有文件块的副本数目小于设置值,如果小于就自动开始复制新的副本并分发到其他DataNode节点。
2.检测文件块的完整性
HDFS会记录每个新创建文件的所有块的校验和。当以后检索这些文件时或者从某个节点获取块时,会首先确认校验和是否一致,如果不一致,会从其他DataNode节点上获取该块的副本。
3.集群的负载均衡
由于节点的失效或者增加,可能导致数据分布不均匀,当某个DataNode节点的空闲空间大于一个临界值的时候,HDFS会自动从其他DataNode迁移数据过来。
4.NameNode上的FSImage和Edit Log文件
NameNode上的FSImage和Edit Log文件是HDFS的核心数据结构,如果这些文件损坏了,HDFS将失效。因而,NameNode由Secondary NameNode定期备份FSImage和Edit Log文件,NameNode在Hadoop中确实存在单点故障的可能,当NameNode出现机器故障,手工干预是必须的。
5.文件的删除
删除并不是马上从NameNode移出命名空间,而是存放在/trash目录随时可恢复,直到超过设置时间才被正式移除。设置的时间由hdfs-site.xml文件的配置项fs.trash.interval决定,单位为秒。