第六章 HDFS概述
6.1.2 HDFS体系结构
HDFS采用主从结构,NameNode(文件系统管理者,负责命名空间,集群配置,数据块复制),
DataNode(文件存储的基本单元,以数据块形式保存文件内容和数据块的数据校验信息,执行底层数据块IO操作),
Client(和名字节点,数据节点通信,访问HDFS文件系统,操作文件),
SecondaryNameNode
1、数据块
Linux的Ext3块默认的大小4096字节,HDFS块默认64M,副本数为3,数据块的好处:文件保存在不同的磁盘上,简化存储子系统,方便容错
和数据复制
2、名字节点和第二名字节点
名字节点维护着整个文件系统的文件目录树,文件目录的元信息和文件的数据块索引。这种信息以两种形式存储在本地文件系统中,
一种是文件系统镜像(FSImage,保存某一特定时刻的信息),一种是文件系统镜像的编辑日志(EditLog,保存改动信息)
运行时客户端通过名字节点获取上述信息,然后和数据节点进行交互,读写文件数据。Namenode获取HDFS整体运行状态的一些信息
如已用空间,未用空间,Datanode的状态。SecondNameNode是用于定期合并命名空间镜像和镜像编辑日志的守护进程,Secondnamenode
不接收或记录HDFS的任何实时变化,而是根据集群配置的时间间隔,不停地获取HDFS某一时间点的命名空间镜像和镜像的编辑日志,合并
到一个新的命名空间镜像,该镜像会传到名子节点,替换原有的命名空间镜像并清空编辑日志。
名字节点单点故障,HDFS HA
3、数据节点
DataNode守护进程将HDFS数据块写到Linux本地文件系统的实际文件,或者从实际文件中读取数据块。客户端进行文件内容操作时,
先由namenode告知client每个数据块在哪个数据点,然后客户端直接与数据节点通信,处理数据块对应的本地文件。
datanode会和其他datanode通信,复制数据,保证数据的冗余性。
datanode作为从节点,会不断的向namenode报告。初始化时datanode将当前存储的数据块告知名字节点,后续datanode不断地更新
namenode,提供本地修改的信息,并接受来自namenode的指令,创建、移动、删除本地磁盘的数据块。
hadoop分割文本的代码得自己写,怎么分都可以,一般是用MapReduce缺省的处理程序,也就是去头补尾方式,按字节拆分后,
从拆分点读到回车符才算正式开始这一段,读到结束点后再继续读到下一个回车符才算正式结束当前段。这样可以保证每一段
都是整行数据构成。除了Hadoop外,集算器的拆分方案也一样,直接分段并行处理文本文件。
4、客户端
客户端是用户和HDFS进行交互的手段,包括命令行,java API,THrift接口等
DistributedFileSystem继承自org.apache.hadoop.fs.FileSystem实现了处理HDFS文件和目录的相关事务。DFSDataInputStream和
DFSDataOutputStream分别实现了FSDataInputStream和FSDataOutputStream提供了读写HDFS的输入输出流。FileStatus可获取文件的
状态
6.1.3 HDFS源代码结构
hdfs源代码在org.apache.hadoop.hdfs包下
1、基础包。hdfs.security.token.block和hdfs.security.token.delegation结合Hadoop安全框架,集成kerberos标准
2、HDFS实体包。hdfs.server.common包含名字节点和数据节点共享的功能,如系统升级和存储空间信息等
hdfs.protocol提供了HDFS各个实体间通过IPC交互的接口
hdfs.server.namenode,hdfs.server.datanode和hdfs分别包含了名字节点、数据节点和客户端的实现
hdfs.server.namenode.metrics和hdfs.server.datanode.metrics实现了名字节点和数据节点上度量数据的收集功能。度量数据包括
名字节点进程和数据节点进程上事件的计数
3、应用包hdfs.tools和hdfs.server.balancer,这两个包提供查询hdfs状态信息工具dfsadmin、文件检查工具fsck和HDFS均衡器
balancer的实现
6.2 基于远程过程调用的接口
HDFS的体系结构包括了名字节点、数据节点和客户端3个主要角色,它们之间有两种主要的通信接口:
Hadoop远程过程调用接口,基于TCP或http的流式接口
HDFS各个节点的IPC接口分为三大类:
(1)客户端相关的接口,定义在org.apache.hadoop.hdfs.protocol包中,具体接口ClientProtocol客户端和名字节点的接口
ClientDatanodeProtocol客户端和数据节点的接口
(2)服务器之间的接口
DatanodeProtocol数据节点和名字节点的接口,InterDatanodeProtocol数据节点和数据节点间的接口
NamenodeProtocol第二名字节点、Hdfs均衡器与名字节点之间的接口
6.2.1 客户端相关的接口
数据块在hdfs中的抽象是org.apache.hadoop.hdfs.protocol.Block,它包含三个成员变量,都是长整型
blockID 数据块的唯一标识,即数据块ID,数据块名blk_
numBytes 数据块文件数据大小
generationStamp 数据块的版本号,即数据块时间戳
BlockLocalPathInfo客户端发现要读取的数据块正好位于同一台机器上时,它可以不通过数据节点读取数据块,而是直接读取本地文件。
ClientProtocol接口函数:
客户端通过addBlock(),向名字节点申请一个新的数据块,addBlock返回一个数据节点的地址,开始写数据前,
对应的数据节点因为某种原因崩溃,客户端联系不上这个节点,客户端会通过abandonBlock()通知名字节点,放弃此数据块,
并再次调用addBlock()方法申请新的数据块,并将原有的数据节点的信息放进参数中,保证名字节点不返回崩溃的节点
fsnyc()保证名字节点对元数据的修改被保存到磁盘,不保证数据节点数据的持久化
complete()用于实现输出流的close()方法,只和名字节点通信,告知客户端已经完成写文件的操作
客户端崩溃,客户端调用ClientProtocol.renewLease(),向名字节点发送心跳信息,如果名字节点长时间没有收到客户端租约的更新
,就认为客户端已经崩溃,名字节点会试图关闭文件。如果客户端从崩溃中恢复过来,并试图继续未完成的写文件操作,这时候
recoverLease()用于恢复租约(带租约恢复的文件路径),如果方法返回true,表明这个文件已经成功关闭,客户端可以通过append()
打开文件,继续写数据。
名字节点崩溃,客户端创建文件或通过追加打开文件时,名字节点会将这些变化记录到命名空间的编辑日志中,名字节点根据日志,
恢复名字节点上的租约信息。
6.2.2 HDFS各个服务器间的接口
1、DatanodeProtocol 用于数据节点和名字节点间的通信
数据节点在初始化时,就会将当前存储的数据块告知名字节点,后续过程中数据节点仍会不断地更新名字节点,为之提供本地数据块
的变化信息,并接受来自名字节点的指令,创建、移动、或者删除本地磁盘的数据块
握手(versionRequest()检查名字节点和数据节点的buildVersion)、注册(resister()提供数据节点的节点标识和存储系统信息)、
数据块上报(blockReport()上报它所管理的全部数据块信息帮助名字节点建立数据块和数据节点的映射关系)、
心跳(sendHeartbeat()除了携带标记身份的信息还包括当前运行情况的信息,名字节点返回DatanodeCommand数组,带来名字节点的指令)
数据块保存在数据节点上,由于种种原因导致数据块损坏,hdfs使用循环冗余校验进行错误检测(三种情况下会校验,数据节点接收数据存储数据前,
客户端读取数据节点上的数据时;数据节点定期扫描数据块),校验出错时就会通过reportBadBlocks()上报给名字节点
2、InterDatanodeProtocol
提供数据恢复的方法
3、NamenodeProtocol
提供方法getBlocks(),均衡器可获得某一数据节点上的一系列数据块及位置,根据这些返回值,均衡器可以把数据块从该数据节点移动到其他数据
节点,平衡各数据节点数据块的目的
getEditLogsSize()可获得名字节点上编辑日志的大小,如果编辑日志达到一定大小,第二名字节点通过rollEditLog()方法通知名字节点
开始一次合并过程,这时名字节点会停止使用当前的编辑日志,并启用新的日志文件,以方便第二名字节点通过基于HTTP的流式接口
获取待合并的命名空间镜像和镜像编辑日志。rollEditLog()返回一个合并检查点。合并完成后,第二名字节点通过http上传新的
元数据镜像,最终完成一次元数据合并。
6.3.1
数据节点的非IPC接口
HDFS数据读写Linux本地文件的接口基于TCP而非IPC接口,有利于批量处理数据,提高数据吞吐量。除了数据块读写,数据节点还
提供了数据块替换,数据块拷贝和数据块检查信息读等基于TCP的接口。
(1)读数据
(2)写数据 Hadoop文件系统实现了数据流管道,客户端在发送数据时,将数据发送到第一个数据节点,然后第一个数据节点在本地
保存数据,同时推送数据到数据节点2,直到管道中的最后一个数据节点,确认包由最后一个数据节点产生,并逆流往客户端方向
回送,沿途的数据节点在确认本本地写成功后,才往上游传递应答
6.3.2 名字节点和第二名字节点上的非IPC接口
ps面向服务的体系结构(SOA),常见的http请求方法有get,post,head,put,delete
Hadoop 1.x 名字节点和第二名字节点间采用http协议和get方法
在Hadoop 2.x中解决了NameNode的单点故障问题;同时SecondaryName已经不用了,而之前的Hadoop 1.x中是通过SecondaryName
来合并fsimage和edits以此来减小edits文件的大小,从而减少NameNode重启的时间。而在Hadoop 2.x中已经不用SecondaryName,
那它是怎么来实现fsimage和edits合并的呢?首先我们得知道,在Hadoop 2.x中提供了HA机制(解决NameNode单点故障),可以
通过配置奇数个JournalNode来实现HA,如何配置今天就不谈了!HA机制通过在同一个集群中运行
两个NN(active NN & standbyNN)来解决NameNode的单点故障,在任何时间,只有一台机器处于Active状态;另一台机器是处于
Standby状态。Active NN负责集群中所有客户端的操作;而Standby NN主要用于备用,它主要维持足够的状态,如果必要,可以
提供快速的故障恢复。
为了让Standby NN的状态和Active NN保持同步,即元数据保持一致,它们都将会和JournalNodes守护进程通信。
当Active NN执行任何有关命名空间的修改,它需要持久化到一半以上的JournalNodes上(通过edits log持久化存储),
而Standby NN负责观察edits log的变化,它能够读取从JNs中读取edits信息,并更新其内部的命名空间。
一旦Active NN出现故障,Standby NN将会保证从JNs中读出了全部的Edits,然后切换成Active状态。
Standby NN读取全部的edits可确保发生故障转移之前,是和Active NN拥有完全同步的命名空间状态
那么这种机制是如何实现fsimage和edits的合并?在standby NameNode节点上会一直运行一个叫做CheckpointerThread的线程,
这个线程调用StandbyCheckpointer类的doWork()函数,而doWork函数会每隔
Math.min(checkpointCheckPeriod, checkpointPeriod)秒来坐一次合并操作
步骤可以归类如下:
(1)、配置好HA后,客户端所有的更新操作将会写到JournalNodes节点的共享目录中,可以通过下面配置
(2)、Active Namenode和Standby NameNode从JournalNodes的edits共享目录中同步edits到自己edits目录中;
(3)、Standby NameNode中的StandbyCheckpointer类会定期的检查合并的条件是否成立,如果成立会合并fsimage和edits文件;
(4)、Standby NameNode中的StandbyCheckpointer类合并完之后,将合并之后的fsimage上传到Active NameNode相应目录中;
(5)、Active NameNode接到最新的fsimage文件之后,将旧的fsimage和edits文件清理掉;
(6)、通过上面的几步,fsimage和edits文件就完成了合并,由于HA机制,会使得Standby NameNode和Active NameNode都拥有最新的fsimage和edits文件(之前Hadoop 1.x的SecondaryNameNode中的fsimage和edits不是最新的)
6.4 HDFS主要流程
6.4.1 客户端到名字节点的文件和目录操作
客户端到名字节点的大量元数据操作,如rename,mkdir等,这些操作一般只涉及客户端和名字节点的交互,通过ClientProtocol
进行。当客户端调用HDFS的FileSystem实例,也就是DistributedFileSystem的mkdir()方法,DistributedFileSystem对象通过IPC
调用名字节点上的远程方法mkdir(),让名字节点执行具体的创建子目录操作,在目录树数据结构上的对应位置创建新的目录节点
同时记录这个操作持久化到日志中,方法执行成功后,mkdir()返回true,期间客户端和名字节点都不需要和数据节点交互。
增加文件副本和删除HDFS上的文件。以客户端删除HDFS文件为例,名字节点执行delete()方法时,它只标记操作涉及的需要被删除
的数据块,也会记录delete操作并持久化到日志,而不会主动联系保存这些数据块的数据节点,立即删除数据。当保存着这些数据块的
数据节点发送心跳时,在心跳应答里,名字节点会通过DatanodeCommand命令数据节点删除数据。
ps:被删除文件的数据,也就是该文件对应的数据块,在删除操作完成后的一段时间后,才会被真正删除;名字节点和数据节点永远维持
着简单的主从结构,名字节点不会向数据节点发起任何IPC调用,数据节点需要配合名字节点执行的操作,都是通过数据节点心跳应答
中携带的DatanodeCommand数组返回。
6.4.2 客户端读文件
客户端通过FileSystem.open()打开文件,对应的HDFS具体文件系统,DistributedFileSystem创建输出流FSDataInputStream,返回
客户端。对HDFS来说,具体的输入流是DFSInputStream,输出流实例通过ClientProtocol.getBlockLocations()远程接口调用名字
节点,以确定文件开始部分数据块的保存位置,对于文件中的每个块,名字节点返回保存着该块副本的数据节点地址。这些数据节点
根据它们与客户端的距离(利用网络的拓扑信息),进行简单排序。
客户端调用FSDataInputStream.read()方法读取文件数据时,DFSInputStream对象会通过和数据节点的“读数据”流接口,和最近的
数据节点建立联系。客户端反复调用read()方法,数据会通过数据节点和客户端连接上的数据包返回客户端。当到达块的末端时,
DFSInputStream会关闭和数据节点的连接,并通过getBlockLocations()远程方法获得保存着下一数据块的数据节点信息(对象没有
缓存该数据块的位置时,才会使用这个远程方法),然后继续寻找最佳数据节点,再次通过数据节点的读数据接口,获得数据。
客户端读文件如果发生错误,如节点停机或网络出现故障,那么客户端会尝试下一数据块位置。同时记住那个出现故障的那个节点,
不在进行徒劳无益的尝试。读数据的应答包中,不但包含了数据,还包含了数据的校验和,客户端会检查数据的一致性,如果校验
有错,也就说数据块已损坏,它会将这个信息报告给名字节点,同时尝试从别的数据节点读取另一个副本的文件内容。
客户端直接联系名字节点,检索数据存放位置,并有名字节点安排数据节点读取顺序,这样做的好处是,能够将读取文件引起的数据传输
分散到各个数据节点,HDFS可以支持大量的并发客户端。同时,名字节点只处理数据块定位请求,不提供数据。
6.4.3 客户端写文件
客户端调用DistributedFileSystem的create()方法创建文件,这时DistributedFileSystem创建DFSOutputStream,并由远程过程调用
,让名字节点执行同名方法,在文件系统的命名空间中创建一个新文件。名字节点创建新文件时,需要执行各种各样的检查,检查完成
后,名字节点会构建一个新文件,并记录创建操作到编辑日志edits中,远程方法调用结束后,DistributedFileSystem将该DFSOutputStream
对象包裹在FSDataOutputStream实例中,返回给客户端。
客户端写入数据时,由于create()调用创建了一个空文件,所以DFSOutputStream实例首先需要向名字节点申请数据块,addBlock()
方法执行成功后,返回一个LocatedBlock对象。该对象包含了新数据块的数据块标识和版本号,同时它的成员变量LocatedBlock.locks
提供了数据流管道的信息,通过上述信息,DFSOutputStream就可以和数据节点联系,通过写数据接口建立数据流管道。客户端写入
FSDataOutputStream流中的数据,被分成一个一个的文件包,放入DFSOutputStream对象的内部队列。该队列中的文件包最后打包成
数据包,发往数据流管道,流经管道上的各个数据节点并持久化,确认包逆流而上,从数据管道依次发往客户端,当客户端收到应答时,
它将对应的包从内部队列移除。
DFSOutputStream在写完一个数据块后,数据流管道上的节点,会通过和名字节点的DatanodeProtocol远程接口的blockReceived()方法
向名字节点提交数据块。如果数据队列中还有等待输出的数据,DFSOutputStream对象需要再次调用addBlock()方法,为文件添加新的数据
块。客户端完成数据的写入后,调用close()方法关闭流,当DFSOutputStream数据队列中的文件包都收到应答后,就可以使用ClientProtocol.complete()
方法通知名字节点关闭文件,完成文件写流程。
如果文件数据写入期间,数据节点发生故障,则会执行以下操作:首先数据管道会被关闭,已经发送到数据管道但还没有收到确认包
的文件包,会重新添加到DFSOutputStream的输出队列。当前正常工作的数据节点上的数据块会被赋予一个新的版本号,并通知名字节点
这样失败的数据节点从故障中恢复过来以后,上面只有部分数据的数据块会因为数据块版本和名字节点保存的版本号不一致而删除。
然后,在数据流管道中删除错误数据节点并重新建立管道,并正常写数据到正常工作的数据节点。文件关闭后,名字节点会发现该数据块的
副本数没有达到要求,会选择一个新的数据节点并复制数据块,创建新的副本。数据节点故障只会影响一个数据块的写操作,后续数据块
写入不会受到影响。数据块写入过程中可能出现多于一个数据节点出现故障的情况,这时只要数据管道中的数据节点数满足配置项${dfs.replication.min}的
值(默认值是1),就认为写操作是成功的。