接下来学习了HDFS读写流程,这里记录一下。
HDFS-NameNode-DataNode之间的通信方式
在了解HDFS读写操作前,了解到HDFS Client是运行在JVM中,它也可以叫做Client Node,这个Client可能在DataNode上,他们之间相互通信方式如上图。
(1)HDFS Client和NameNode之间是RPC通信
(2)NameNode和DataNode之间是RPC通信
(3)HDFS Client和DataNode之间是普通Socket通信
HDFS读操作
Read概括:
client端如果要读取某一个文件,由于这个文件被拆分成block后存在于不同的DataNode上面,刚开始它是不知道在哪里读取的,因此需要求助于NameNode告诉它这个文件分为几个block,都存在于哪些地方。NameNode返回结果给client端后,client端会根据得到的block块存储信息,对DataNode中的数据进行读取。
Read细节:
1 运行在JVM虚拟机中的HDFS Client,会调用Hadoop下的DistributedFileSystem的open方法返回FSDataInputStream对象。
2 在返回FSDataInputStream对象时,会通过client端ClientProtocol接口getBlockLocations方法RPC调用NameNode的同名方法返回block的信息到对象中。可以看出ClientProtocol的作用就是用于用户使用DistributedFileSystem类来建立和NameNode的RPC通信,并可以使用方法来操作namespace的文件目录。
3 查看getBlockLocations方法,其调用后将返回给JVM一个LocatedBlocks类型的对象,它包含块的DataNode位置信息以及block的长度信息,并会根据所在的DataNode和client的距离进行排序,最近的会排在前面。
4 5 6 然后Client端调用FSDataInputStream对象的read方法,开始读取最近一个DataNode上block的数据(如果client在DataNode01并且上面也有它要读取的block那就是先读DataNode01上的数据了),并会对返回的数据检查checksum(使用CRC32算法),即检查Datanode上block的checksum和读取到的block数据的checksum,如果两者一致就会结束本次block的读取,接着读取剩余的block。
7 如果上面步骤检查checksum发现有问题或者连接发生中断,就会从包含这个block副本的临近DataNode上读取,并且也需要检查checksum。由于HDFS有心跳机制,NameNode会对读取失败的DataNode进行记录,下次将不再从它身上读取。
8 读取block完成后,调用FSDataInputStream的close方法,结束本次文件读取。
HDFS写操作
Write概括:
client端如果需要向HDFS写一个文件,比如一个300M的文件需要写入HDFS,client是不知道要怎么拆分,存到哪些DataNode上的,因此需要求助于NameNode,NameNode会根据各个数据节点上存储的情况,以及当前文件的大小,计算出一份合理的存储方案,告诉client应该拆分为几个block,分别存在哪几个DataNode。然后client会首先找到一个最近的DataNode,写入一个block,然后这个block会平移复制到其他分配的DataNode,完成一个block块的写入,剩余的block也是进行同样的操作。
Write细节:
1 运行在JVM虚拟机中的HDFS Client,会调用Hadoop下的DistributedFileSystem的create方法返回DFSOutputStream对象,返回对象的过程中会建立和NameNode的连接,使用DFSClient。
2 3 在返回DFSOutputStream对象之前,使用client上ClientProtocal的create方法调用NameNode上NameNodeRpcServer的create方法,在HDFS上建立新文件,在创建时需要确定路径(绝对路径)、确定要写入的文件是否存在、客户端是否有权限创建文件、建立的副本数等信息。
在HDFS上创建完空文件后,会将操作记录在NameNode的editlog中,并给client返回FSDataOutputStream,它是封装了DFSOutputStream的对象。
4 client端会调用DFSOutputStream的write方法开始写数据(没找到write方法?),在写数据前调用ClientProtocol的addBlock方法,返回LocatedBlock对象,它包含block的详细信息,如在哪个DataNode和block长度信息,知道了位置信息就会知道了数据要写入的路径并建立数据流管道pipeline。
另外这个方法执行后也会向NameNode报告文件创建的时间戳。
5 在确定了要写入的block和DataNode位置后就可以开始写数据了,可以参考下图。
5.1 调用DFSOutputStream的writechunk方法写入数据到一个校验块chunk中,包含512byte的数据和4byte的checksum
5.2 校验块chunk会添加到更大单位的packate中,大小为64KiB
5.3 一个个的packate会添加到一个dataqueue队列中等待写入,可以看到它是一个链表结构,适合插入和删除数据。还会复制一份镜像队列ackQueue,这个队列是验证复制用的。
5.4 一个一个的packate会向pipeline中一次写数据,比如从DataNode01→DataNode02→DataNode03依次写入,并逆向返回ack确认包,如果返回SUCESS,则ackqueue中的镜像packate就会删除,否则会从ackqueue取出对应packate到dataqueue尝试重新发送。
5.5 当block中所有的数据按照上面流程写完后,会发送一个空的packate代表写完了,关闭当前block的pipeline,其他的block写入流程类似。
6 写完所有的block后,需要验证client端block的checksum和写入到DataNode的checksum是否一致。如果某个block的checksum检查不一致,就会执行更新block时间戳、删除block、更新pineline、申请新的DataNode并复制block、更新namenode元数据等操作,如果检查没问题就继续执行下面第7和第8步的操作。
6.1 输出流DFSOutputStream中ackqueue所有的数据会重新加入dataqueue
6.2 在故障期间,输出流调用ClientProtocol的upateBlockForPipeLine方法,为block申请新的时间戳并保存在NameNode,就算DataNode上的block数据OK了,DataNode上block的时间戳和NameNode中保存的不一致就删除这个block。
6.3 输出流调用ClientProtocol的getAdditionalDataNode方法,让NameNode分配新的DataNode到pipeline中,并使用上面新的时间戳给这个block。并且这个方法也会返回LocatedBlock,即知道了要重新写入的block详细信息。
6.4 有了新的DateNode,也知道要写入的block信息,接下来client就调用DataTransferProtocol通过pipeline中的一个正常DateNode复制这个block信息到新的DataNode,使用transferBlock方法?
6.5 复制完成后并重新pipeline,输出流调用ClientProtocol的updatePipeline方法,更新NameNode中的元数据。
7 如果一致就调用DFSOutputStream的close方法关闭流
9 client端使用ClientProtocol的complete方法RPC调用NameNode的complete方法,完成本次写操作
以上为HDFS读写流程的分析,后续继续完善。