HDFS备忘

HDFS文件系统

一、HDFS设计

    HDFS为hadoop的核心组件,为hadoop底层的文件存储系统。它通常运行在商用硬件集群上,用来存储超大文件、大规模文件。

    1、超大文件

    "超大文件"是指具有数百MB、数G甚至数T大小的文件;HDFS设计为了存储大量“超大文件”。

    2、流式数据访问

    HDFS的文件为“一次写入”、“多次读取”,这种高效的文件访问策略是HDFS的设计核心。被创建和写入的文件,将长时间存储在HDFS中,用来做数据分析计算。通常以流式访问整个文件或者文件的一部分。

    3、商用硬件

    Hadoop不需要运行在专业的昂贵的硬件上。它是设计运行在普通的(廉价)的商用机器上,并构架大规模集群。通常这些硬件都有普遍的故障率,HDFS提供了很多方案来提高文件的可用性。

    4、低时间延迟的数据访问

    HDFS不适合“低延迟”要求的数据访问,例如实时在线的数据访问,要求几毫秒访问延迟,这种场景是不适合HDFS的。HDFS以牺牲数据访问延迟性要求,达到“数据高吞吐量”优先的目标。

    5、大量小文件

    由于Namenode将文件系统的metada内存在内存中,因此文件系统所能存储的文件总数受限于namenode的内存总量。每个文件、目录、Block相关的metadata大概有150个字节,因此,一个Namenode

所能支持的文件个数是有上限的。因此HDFS并不适合存储大量小文件(数KB,数M),因为它们虽然不会消耗大量的磁盘空间,但是却潜在的增加了Namenode的负荷比。

    6、多用户写入,任意修改文件

    HDFS中的文件只有一个writer,而且write操作总是将数据append到文件的末尾。它不支持多个writer同时写入,也不支持任意位置的随机写,因为这是低效的,这归因与HDFS文件存储方式和Block的设计原理。

二、HDFS概念

    1、Block

    Block即为数据块,对于磁盘而言,也有block的概念,它是磁盘数据读写的最小单位,一般为512B,这是文件系统提升磁盘读取性能的方式。对于HDFS而言,Block有更多的含义,它比磁盘Block大的多,一般为64M甚至更大(128M,后期版本,默认为256M);一个HDFS文件在存储层面,会被分割成多个blocks片段,每个block作为独立的存储单元(replication)。与其他文件系统不同(包括磁盘存储),一个小于block大小的文件不会占用整个块空间。较大的block,对于Namenode管理文件而言是有益的,也可以有效的提高文件数据的读取性能。不过在Mapreduce应用中,此值还不能设置的过大,因为Map任务通常一次处理一个block数据,如果block过大会导致map的任务个数较少(少于集群节点数量),会在一定程度上影响mapreduce数据并行计算能力,拖慢job的整体运行速度。

    HDFS通过将一个文件在物理存储上分割成多个block,并分别将它们分布存储在集群的多个节点上,这种特性可以让HDFS保存一个大小超过本地磁盘容量的超大文件。以Block作为文件的物理存储单位,还大大简化了

存储子系统的设计,简化了存储系统管理;因为一个文件的block大小是固定的(可配置,一旦创建,则不能修改此参数),因此一个节点上能存储多少个block以及某个Block是否可以被放置是显而易见的。

    此外,Block这种小尺寸的数据块,可以很方便的replication,从而让HDFS文件的高可用和容错特性的实现更加简单。将每个Block复制到少数几个node上(默认为3),可以确保当某个Block被破坏或者节点软硬件故障时数据不会丢失。如果一个block不可用,系统将会从其他节点读取一个现有的block副本,如果某个Block的副本个数低于配置(3),那么Namenode将会为此block在合适的Node上创建新的副本,以保证Block的个数处于正常水平。

    我们可以通过“hdfs fsck <path>”指令获取有关Block分布的相关细节。

    2、Namenode与Datanodes

    HDFS中有两类节点,并以master-slave模式运行,即一个Namenoede和多个Datanodes。其中namenode作为master(角色),主要职责是管理集群的namespace,和协调与namespace变更有关的操作(block复制,rebalance等);它维护着文件系统树以及整棵树内所有的文件和目录。这些信息以两个文件形式持久保存在本地磁盘上:fsimage和Editlog。Namenode也记录着每个文件中所有block所在的数据节点信(blockMapping),blockMapping信息不会持久保存,因为这些信息会在系统启动时(运行时)由Datanodes通过心跳中的blockReport来反馈。

    Datanode是文件系统的存储节点,在HDFS架构模型中,它作为slaves存在。它负责存储文件的实际数据--Blocks,并与Namenode注册链接,通过心跳的方式向Namenode发送BlockReport信息(持有的blocks列表);同时Client可以直接与Datanode交互并访问指定名称的block中的数据。

    用户Application通过HDFS Client与Namenode和Datanodes交互来访问整个文件系统。HDFS提供了类似于POSIX的文件系统接口,因此用户在使用API接口时无需了解Namenode、Datanode的细节实现。Client在访问文件时,首先与Namenode通信并获取文件所有(或一批)Blocks列表以及它们的位置信息,此后Client与所需要的Block所在的一个Datanode建立链接并读写数据即可。

    因为Namenode保存了集群的namespace,是Client与Datanode的调度中心,因此如果Namenode失效,那么整个文件系统将不可用,只有Datanodes是无法重建集群的。Hadoop提供了多种容错方案用来解决Namenode的单点问题:SecondaryNamenode,CheckpointNode,BackupNode,还有更加复杂的HDFS HA架构(Standby Namenode)。它们的核心思想就是将Namenode中的namespace同步的在多个节点上备份,以便在故障时可以通过副本恢复集群状态。

三、HDFS接口

    1、读取数据

public void read(String path) throws Exception{
	Configuration conf = new Configuration();
	FileSystem fs = FileSystem.get(conf);
	BufferedReader reader = null;
	try{
		FSDataInputStream fsInput = fs.open(new Path(path));
		reader = new BufferedReader(new InputStreamReader(fsInput,"UTF-8"),256);
		String line = null;
		while (true) {
			line = reader.readLine();
			if(line == null) {
				break;
			}
			System.out.println(line);
		}
	}catch (Exception e) {
		e.printStackTrace();
	} finally {
		IOUtils.closeStream(reader);
	}
}

    FileSystem#open(path)方法可以用来打开一个文件,此方法返回FSDataInputStream对象,这个类继承了DataInputStream类,同时还实现了Seekable、PositionedReadable接口,它表达了可以从任何位置读取文件。

Seekable接口中seek方法允许跳跃到文件的任何位置作为起点读取数据。不过seek方法相对开销较大,不建议频繁的使用。

    2、写入数据

public void create(String path,String file) throws Exception {
	Configuration conf = new Configuration();
	FileSystem fs = FileSystem.get(conf);
	BufferedWriter writer = null;
	try {
		FSDataOutputStream fsOut = fs.create(new Path(path,file),true);
		writer = new BufferedWriter(new OutputStreamWriter(fsOut,"UTF-8"));
		writer.write("Hello,hadoop!");
		writer.newLine();
		writer.write("Hello,HDFS!");
		writer.flush();
	}catch (Exception e) {
		e.printStackTrace();
	} finally {
	   writer.close();
	}
}

    通过create方法创建文件,此方法有多个重载版本,可以在创建文件时指定权限、block大小、buffer大小等。创建文件时,如果父路径不存在则会创建,很方便。对于已经创建的文件,可以使用append方法在文件的尾部追加数据(继续写入)。create方法和append方法都会返回FSDataOutputStream对象,它继承自DataOutputStream,并实现了Syncable接口。与FSDataInputStream不同,它不允许通过seek的方式在任意位置写入数据。

    此外FileSystem中提供了大量API,可以进行文件的读写、目录操作、文件状态查看等。

三、Hadoop IO

    1、数据完整性

    HDFS中的数据都会被可靠的存储,庞大的Hadoop集群中因为磁盘、网络IO的故障或者系统的bug,可能会因此错误而导致文件损坏,hadoop中通过使用Checksum来检测文件数据的完整性。当数据第一次引入系统时计算Checksum,并在数据通过一个不可靠的通道进行传输时再次计算校验和,通过比较Checksum值即可知道数据在传输中是否损坏。如果校验和值不同,我们认为数据已损坏。常用的CheckSUM算法是CRC-32(循环冗余码校验)。

    HDFS会在写入数据时计算校验和,并在读取数据时验证校验和。校验和所占的开支很小。

    Datanode负责在验证收到的数据后存储数据极其校验和值,比如从Client接受数据以及在replication期间。正在写数据的Client将数据及其校验和发送到由一系列Datanodes组成的pipeline中,管线中最后一个datanode负责验证校验和。如果Datanode检测到错误,客户端会收到一个ChecksumException,操作将会被重试。

    客户端读取数据时,也会验证校验和,将它们与datanode中存储的校验和进行比较。每个Datanode均持久保存了一个用户验证的校验和日志,所以它知道每个数据块的最后一次验证的时间。客户端成功验证一个数据块后,会告诉这个Datanode,datanode由此更新日志,保存这个统计信息对于检测损坏的磁盘很有价值。每个Datanode也会在后台运行一个DataBlockScanner,定期验证此节点上所有的数据,该措施是解决物理存储魅力上损坏的有力措施。

    因为HDFS存储着每个block的replica,因此可以通过复制完好的数据block来修复损坏的快,从而得到一个新的、完好的复本。在Client在读操作时,如果检测到错误,则会向Namenode报告已损坏的block以及所在的Datanode,最后才抛出ChecksumException异常。Namenode将这个block标记为“损坏”(metada中),所以并不需要直接与Datanode联系,此后,Namenode会调度block的replication机制,将完好的Block重新再集群中复制(那个损坏的block将会被删除),从而保证了block的replication factor处于期望水平。

    可以在读取文件之间,通过FileSystem#setVerifyChecksum(boolean)方法来设定是否启用Checksum校验。

    2、压缩

    文件压缩可以减少存储文件所需要的磁盘空间;可以加速数据在网络和磁盘上的传输;当需要处理大量数据时,压缩特性将显得非常重要。Hadoop支持多种压缩算法:DEFLATE、Gzip,bizp2、LZO等。其中前三者Hadoop中已经有JAVA实现,包含在hadoop发布包中。LZO库需要单独下载。其中只有bzip2支持切分。在默认情况下,Hadoop会从运行平台中搜索原生代码库,如果找到就会自动加载并使用。我们可以通过将“hadoop.native.lib”配置项设定为false,来禁用本地库,此时将会使用内置的JAVA代码库(如果有的话)。

    所谓切分,就是压缩后的文件可以拆分为多个子文件片段存储,可以将任何子文件片段独立解压。目前只有bzip2算法支持拆分。那么在Mapreduce处理数据时,压缩文件是否支持拆分将是很重要的。例如一个压缩前1G的文件,block大小为64M,那么次文件将会被存储在16个block中;那么每个block将会有一个mapper任务读取。现在经过压缩后大小为1G的文件,存储在16个block中(数据流压缩后,存储HDFS,仍为64M一个Block)。那么一个压缩后的文件将由16个block文件片段组成,如果压缩算法不支持切分,那么将不能对任何一个block单独解压。Mapreduce不会尝试切分那些由不支持切分的压缩文件,那么任何一个mapper都需要额外的解压相应的block之前的所有blocks,才能够解压并读取当前block的数据,这就牺牲了block的本地性。

    对于巨大的、没有边界的文件,比如日志文件,可以考虑:

    1) 存储未经压缩的文件。

    2) 使用支持切分的压缩格式,比如bzip2。

    3) 使用SequeueFile,它支持压缩和切分。

    对于大文件而言,不应该使用不支持切分整个文件的压缩格式,否则将会失去数据的本地特性,导致mapreduce应用效率低下。不过对于mapper产生的临时数据以及reducer产生的结果数据,仍然推荐使用压缩,这可以提高数据在mapper与reducer之间的传输性能(mapper结果数据为保存在本地文件,而非HDFS中)。

猜你喜欢

转载自shift-alt-ctrl.iteye.com/blog/2107091