3.4 HDFS的数据读写过程
在介绍HDFS数据读写过程之前,需要简单介绍一下相关的类。File System是一个通用文件系统的抽象基类,可以被分布式文件系统继承,所有可能使用Hadoop文件系统的代码都要使用到这个类。Hadoop为File System这个抽象类提供了多种具体的实现,Distributed File System就是File System在HDFS文件系统中的实现。FileSystem的open()方法返回的是一个输入流FS Data Input Stream对象,在HDFS文件系统中,具体的输入流就是DFS Input Stream;File System中的create()方法返回的是一个输入流FS Data Output Stream对象,在HDFS文件系统中,具体的输出流就是DFS Output Stream。
3.4.1 读数据的过程
1.概述
客户端将要读取的文件路径发送给NameNode,NameNode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应DataNode逐个获取文件的block并在客户端本地进行数据追加合并,从而获得整个文件。
2.读数据步骤详解
1)如图3-23所示客户端向NameNode发起RPC调用,请求读取文件数据。
2)NameNode检查文件是否存在,如果存在则获取文件的元信息(blockID以及对应的DataNode列表)。
图3-23 读文件
3)客户端收到元信息后,选取一个距离网络最近的DataNode,依次请求读取每个数据块。客户端首先要校检文件是否损坏,如果损坏,客户端会选取另外的DataNode请求。
4)DataNode与客户端socket连接,传输对应的数据块,客户端收到数据缓存到本地之后写入文件。
5)依次传输剩下的数据块,直到整个文件合并完成。
从某个DataNode获取的数据块有可能是损坏的,损坏可能是由DataNode的存储设备错误、网络错误或者软件bug造成的。HDFS客户端软件实现了对HDFS文件内容的校验和检查。当客户端创建一个新的HDFS文件,会计算这个文件每个数据块的校验和,并将校验和作为一个单独的隐藏文件保存在同一个HDFS名字空间下。当客户端获取文件内容后,它会检验从DataNode获取的数据跟相应的校验和文件中的校验和是否匹配,如果不匹配,客户端可以选择从其他DataNode获取该数据块的副本。
3.4.2 写数据的过程
1.概述
客户端要向HDFS写数据,首先要跟NameNode通信以确认可以写文件并获得接收文件block的DataNode,然后客户端按顺序逐个将block传递给相应DataNode,并由接收到block的DataNode负责向其他DataNode复制block的副本。
2.写数据步骤详解
1)如图3-24所示,客户端向NameNode发送上传文件请求,NameNode对要上传的目录和文件进行检查,判断是否可以上传,并向客户端返回检查结果。
2)客户端得到上传文件的允许后读取客户端配置,如果没有指定配置则会读取默认配置(例如副本数和块大小默认为3MB和128MB,副本是由客户端决定的)。向NameNode请求上传一个数据块。
3)NameNode会根据客户端的配置来查询DataNode信息,如果使用默认配置,那么最终结果会返回同一个机架的两个DataNode和另一个机架的DataNode。这称为“机架感知”策略。
4)客户端在开始传输数据块之前会把数据缓存在本地,当缓存大小超过了一个数据块的大小时,客户端就会从NameNode获取要上传的DataNode列表。之后会在客户端和第一个Da taNode建立连接开始流式地传输数据,这个DataNode会一小部分一小部分(4KB)地接收数据然后写入本地仓库,同时会把这些数据传输到第二个DataNode,第二个DataNode也同样一小部分一小部分地接收数据并写入本地仓库,同时传输给第三个DataNode,依次类推。这样逐级调用和返回之后,待这个数据块传输完成,客户端会告诉NameNode数据块传输完成,这时候NameNode才会更新元数据信息记录操作日志。
图3-24 写文件
5)第一个数据块传输完成后会使用同样的方式传输下面的数据块,直到整个文件上传完成。
3.HDFS数据删除
HDFS删除数据比较流程相对简单,只列出详细步骤。
1)客户端向NameNode发起RPC调用,请求删除文件。NameNode检查合法性。
2)NameNode查询文件相关元信息,向存储文件数据块的DataNode发出删除请求。
3)DataNode删除相关数据块,返回结果。
4)NameNode返回结果给客户端。
当用户或应用程序删除某个文件时,这个文件并没有立刻从HDFS中删除。实际上,HDFS会将这个文件重命名转移到/trash目录。只要文件还在/trash目录中,该文件就可以被迅速地恢复。文件在/trash中保存的时间是可配置的,当超过这个时间时,NameNode就会将该文件从名字空间中删除。删除文件会使得该文件相关的数据块被释放。注意,从用户删除文件到HDFS空闲空间的增加之间会有一定的时间延迟。只要被删除的文件还在/trash目录中,用户就可以恢复这个文件。如果用户想恢复被删除的文件,可以浏览/trash目录找回该文件。/trash目录仅仅保存被删除文件的最后副本。/trash目录与其他的目录没有什么区别,除了一点:在该目录上HDFS会应用一个特殊策略来自动删除文件。目前的默认策略是删除/trash中保留时间超过6h的文件。将来,这个策略可以通过一个被良好定义的接口配置。
当一个文件的副本系数被减小后,NameNode会选择过剩的副本删除。下次心跳检测时,会将该信息传递给DataNode。DataNode遂即移除相应的数据块,集群中的空闲空间加大。同样,在调用setReplication API结束和集群中空闲空间增加之间,也会有一定的延迟。
4.安全模式
NameNode启动后会进入一个被称为安全模式的特殊状态。处于安全模式的NameNode是不会进行数据块复制的。NameNode从所有的DataNode接收心跳信号和块状态报告。块状态报告包括某个DataNode所有的数据块列表。每个数据块都有一个指定的最小副本数。当NameNode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全(Safely Replicated)的;在一定百分比(这个参数可配置)的数据块被NameNode检测确认是安全之后(加上一个额外的30s等待时间),NameNode将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他DataNode上。
5.细节
1)请求和应答是使用RPC的方式,客户端通过Client Protocol与NameNode通信,NameNode和DataNode之间使用DataNode Protocol交互。在设计上,NameNode不会主动发起RPC,而是响应来自客户端或DataNode的RPC请求。客户端和DataNode之间使用socket进行数据传输,和NameNode之间的交互采用NIO封装的RPC。
2)HDFS有自己的序列化协议。
3)在数据块传输成功后并且客户端没有告诉NameNode之前,如果NameNode宕机,那么这个数据块就会丢失。
4)在流式复制时,逐级传输和响应采用响应队列来等待传输结果。队列响应完成后返回给客户端。
5)在流式复制时如果有一台或两台(不是全部)没有复制成功,并不影响最后结果,只不过DataNode会定期向NameNode汇报自身信息。如果发现异常NameNode会指挥DataNode删除残余数据和完善副本,如果副本数量少于某个最小值就会进入安全模式。