一,什么是分布式文件系统,分布式文件系统能干什么
在学习一个文件系统时,首先我先想到的是,学习它能为我们提供什么样的服务,它的价值在哪里,为什么要去学它。以这样的方式去理解它之后在日后的深入学习中才能够对它有一个更深层次的理解。
1. 什么是分布式文件系统
所谓的分布式文件系统,我的个人理解是管理网络中把数据存储在不止一台机器上的系统,这样的存储方式被称为分布式存储。(管理网络中跨多台计算机存储的文件系统称为分布式文件系统。)
2. 分布式文件系统能干什么
(1)分布式文件系统可以存储数据量较大的文件。 (GB,TB,PB级别的海量数据)
(2)一次写入,多次夺取。(一个文件经过创建、写入和关闭之后就不需要改变。这一假设简化了数据一致性问题,并且使高吞吐量的数据访问成为可能。Map/Reduce应用或者网络爬虫应用都非常适合这个模型。目前还有计划在将来扩充这个模型,使之支持文件的附加写操作。)
(3)搭建在普通的商业集群机器上就可以了,节省成本
二,HDFS分布式文件系统的生态环境
上图基本可以说是目前整个Hadoop的生态圈结构图,从图中可以看出像Hive,Pig,HBase等数据处理框架都是应用在我们的HDFS分布式文件系统之上的。而且MapReduce计算框架也运行在HDFS上,对数据的上传下载分析起到了至关重要的作用。
另外很多童鞋会把hdfs等价于hadoop的文件系统,其实hadoop是一个综合文件系统抽象,而hdfs是hadoop旗舰级文件系统,hadoop除了hdfs还能集成其他文件系统。Hadoop的这个特点充分体现了hadoop的优良的可扩展性。
在hadoop里,hadoop定义了一个抽象的文件系统的概念,具体就是hadoop里面定义了一个java的抽象类:org.apache.hadoop.fs.FileSystm,这个抽象类用来定义hadoop中的一个文件系统接口,只要某个文件系统实现了这个接口,那么它就可以作为hadoop支持的文件系统。下面是目前实现了hadoop抽象文件类的文件系统,如下表所示:
文件系统 | URI方案 | Java实现 (org.apache.hadoop) | 定义 |
Local | file | fs.LocalFileSystem | 支持有客户端校验和本地文件系统。带有校验和的本地系统文件在fs.RawLocalFileSystem中实现。 |
HDFS | hdfs | hdfs.DistributionFileSystem | Hadoop的分布式文件系统。 |
HFTP | hftp | hdfs.HftpFileSystem | 支持通过HTTP方式以只读的方式访问HDFS,distcp经常用在不同的HDFS集群间复制数据。 |
HSFTP | hsftp | hdfs.HsftpFileSystem | 支持通过HTTPS方式以只读的方式访问HDFS。 |
HAR | har | fs.HarFileSystem | 构建在Hadoop文件系统之上,对文件进行归档。Hadoop归档文件主要用来减少NameNode的内存使用。 |
KFS | kfs | fs.kfs.KosmosFileSystem | Cloudstore(其前身是Kosmos文件系统)文件系统是类似于HDFS和Google的GFS文件系统,使用C++编写。 |
FTP | ftp | fs.ftp.FtpFileSystem | 由FTP服务器支持的文件系统。 |
S3(本地) | s3n | fs.s3native.NativeS3FileSystem | 基于Amazon S3的文件系统。 |
S3(基于块) | s3 | fs.s3.NativeS3FileSystem | 基于Amazon S3的文件系统,以块格式存储解决了S3的5GB文件大小的限制。 |
最后我要强调一点:在hadoop里有一个文件系统概念,例如上面的FileSystem抽象类,它是位于hadoop的Common项目里,主要是定义一组分布式文件系统和通用的I/O组件和接口,hadoop的文件系统准确的应该称作hadoop I/O。而HDFS是实现该文件接口的hadoop自带的分布式文件项目,hdfs是对hadoop I/O接口的实现。下面我给大家展示一张表,这样大家对hadoop的FileSystem里的相关API操作就比较清晰了,表如下所示:
Hadoop的FileSystem | Java操作 | Linux操作 | 描述 |
URL.openSteam FileSystem.open FileSystem.create FileSystem.append | URL.openStream | open | 打开一个文件 |
FSDataInputStream.read | InputSteam.read | read | 读取文件中的数据 |
FSDataOutputStream.write | OutputSteam.write | write | 向文件写入数据 |
FSDataInputStream.close FSDataOutputStream.close | InputSteam.close OutputSteam.close | close | 关闭一个文件 |
FSDataInputStream.seek | RandomAccessFile.seek | lseek | 改变文件读写位置 |
FileSystem.getFileStatus FileSystem.get* | File.get* | stat | 获取文件/目录的属性 |
FileSystem.set* | File.set* | Chmod等 | 改变文件的属性 |
FileSystem.createNewFile | File.createNewFile | create | 创建一个文件 |
FileSystem.delete | File.delete | remove | 从文件系统中删除一个文件 |
FileSystem.rename | File.renameTo | rename | 更改文件/目录名 |
FileSystem.mkdirs | File.mkdir | mkdir | 在给定目录下创建一个子目录 |
FileSystem.delete | File.delete | rmdir | 从一个目录中删除一个空的子目录 |
FileSystem.listStatus | File.list | readdir | 读取一个目录下的项目 |
FileSystem.getWorkingDirectory |
| getcwd/getwd | 返回当前工作目录 |
FileSystem.setWorkingDirectory |
| chdir | 更改当前工作目录
|
有了这张表,大家对FileSystem的理解应该会清晰多了吧。
大家从对照表里会发现,hadoop的FileSystem里有两个类:FSDataInputStream和FSDataOutputStream类,它们相当于java I/O里的InputStream和Outputsteam,而事实上这两个类是继承java.io.DataInputStream和java.io.DataOutputStream。
至于关于hadoop I/O本文今天不做介绍,以后也许会专门写篇文章讲讲我自己的理解,不过为了给大家一个清晰的印象,我找到了两篇文章,有兴趣的童鞋可以好好看看看
三,HDFS分布式文件系统对数据是如何进行读写的
1. HDFS 写入过程
NameNode负责管理存储在HDFS上所有文件的元数据,它会确认客户端的请求,并记录下文件的名字和存储这个文件的DataNode集合。它把该信息存储在内存中的文件分配表里。
例如,客户端发送一个请求给NameNode,说它要将“zhou.log”文件写入到HDFS。那么,其执行流程如图1所示。具体为:
第一步:客户端发消息给NameNode,说要将“zhou.log”文件写入。(如图1中的①)
第二步:NameNode发消息给客户端,叫客户端写到DataNode A、B和D,并直接联系DataNode B。(如图1中的②)
第三步:客户端发消息给DataNode B,叫它保存一份“zhou.log”文件,并且发送一份副本给DataNode A和DataNode D。(如图1中的③)
第四步:DataNode B发消息给DataNode A,叫它保存一份“zhou.log”文件,并且发送一份副本给DataNode D。(如图1中的④)
第五步:DataNode A发消息给DataNode D,叫它保存一份“zhou.log”文件。(如图1中的⑤)
第六步:DataNode D发确认消息给DataNode A。(如图1中的⑤)
第七步:DataNode A发确认消息给DataNode B。(如图1中的④)
第八步:DataNode B发确认消息给客户端,表示写入完成。(如图1中的⑥)
图1 HDFS写过程示意图
在分布式文件系统的设计中,挑战之一是如何确保数据的一致性。对于HDFS来说,直到所有要保存数据的DataNodes确认它们都有文件的副本时,数据才被认为写入完成。因此,数据一致性是在写的阶段完成的。一个客户端无论选择从哪个DataNode读取,都将得到相同的数据。
2. HDFS 读的过程
为了理解读的过程,可以认为一个文件是由存储在DataNode上的数据块(block)组成的。客户端查看之前写入的内容的执行流程如图2所示,具体步骤为:
第一步:客户端询问NameNode它应该从哪里读取文件。(如图2中的①)
第二步:NameNode发送数据块的信息给客户端。(数据块信息包含了保存着文件副本的DataNode的IP地址,以及DataNode在本地硬盘查找数据块所需要的数据块ID。) (如图2中的②)
第三步:客户端检查数据块信息,联系相关的DataNode,请求数据块(这里涉及到一个就近原则问题,NameNode把数据块信息反馈给客户端后,客户端会选择离自己最近的DataNode上去读取数据)。(如图2中的③)
第四步:DataNode返回文件内容给客户端,然后关闭连接,完成读操作。(如图2中的④)
图2 HDFS读过程示意图
客户端并行从不同的DataNode中获取一个文件的数据块,然后联结这些数据块,拼成完整的文件。
四,通过副本快速恢复硬件故障
当一切运行正常时,DataNode会周期性发送心跳信息给NameNode(默认是每3秒钟一次)。如果NameNode在预定的时间内没有收到心跳信息(默认是10分钟),它会认为DataNode出问题了,把它从集群中移除,并且启动一个进程去恢复数据。DataNode可能因为多种原因脱离集群,如硬件故障、主板故障、电源老化和网络故障等。
对于HDFS来说,丢失一个DataNode意味着丢失了存储在它的硬盘上的数据块的副本。假如在任意时间总有超过一个副本存在(默认3个),故障将不会导致数据丢失。当一个硬盘故障时,HDFS会检测到存储在该硬盘的数据块的副本数量低于要求,然后主动创建需要的副本,以达到满副本数状态。
五,跨多个DataNode切分文件
在HDFS里,文件被切分成数据块,通常每个数据块64MB~128MB,然后每个数据块被写入文件系统。同一个文件的不同数据块不一定保存在相同的DataNode上。这样做的好处是,当对这些文件执行运算时,能够通过并行方式读取和处理文件的不同部分。
当客户端准备写文件到HDFS并询问NameNode应该把文件写到哪里时,NameNode会告诉客户端,那些可以写入数据块的DataNode。写完一批数据块后,客户端会回到NameNode获取新的DataNode列表,把下一批数据块写到新列表中的DataNode上。