在hadoop中作为后端存储的文件系统HDFS发挥中重要的作用,HDFS是一个分布式文件系统,按照Google File System的思想开发的,针对的场景是低端服务器、写操作少而读操作多的情况。在HDFS中为了保证数据的完整和可靠,使用了多种方式,比如采用检验和(checksum)和多副本放置的策略,在HDFS中很常用的检验和方式采用的是CRC(循环冗余校验码)。HDFS是一种基于块的文件系统,采用的基本思想和ext文件系统的基本思想类似,只不过这是一个分布式文件系统。
下面是几个关于HDFS中重要的基本概念:
1、Block: 在HDFS中,每个文件都是采用的分块的方式存储,每个block放在不同的datanode上,每个block的标识是一个三元组(block id, numBytes,generationStamp),其中block id是具有唯一性,具体分配是由namenode节点设置,然后再由datanode上建立block文件,同时建立对应block meta文件(其中的内容,大部分是对应数据的检验和内容)。
2、Chunk:对应的中文名字也可以称为块,但是为了与block区分,还是称之为chunk。在DFSClient与DataNode之间通信的过程中,由于文件采用的是基于块的方式来进行的,但是在发送数据的过程中是以packet的方式来进行的,每个packet包含了多个chunk,同时对于每个chunk进行checksum计算,生成checksum bytes。
3、Packet:在DFSclient与DataNode之间通信的过程中,发送和接受数据过程都是以一个packet为基础的方式进行。
关于HDFS中进行通信的数据包(Packet)的格式如下:
+-----------------------------------------------------------------------+
| 4 byte packet length(exclude packet header) |
+------------------------------------------------------------------------+
| 8 byte offset in the block | 8 byte sequence number |
+------------------------------------------------------------------------+
| 1 byte isLastPacketInBlock |
+------------------------------------------------------------------------+
| 4 byte Length of actual data |
+-------------------------------------------------------------------------+
| x byte checksum data. x is defined below |
+-------------------------------------------------------------------------+
| actual data ......... |
+--------------------------------------------------------------------------+
x = (length of data + BYTE_PER_CHECKSUM - 1) / BYTE_PER_CHECKSUM * CHECKSUM_SIZE
在DFSclient与DataNode进行通信,采用的是经典的C/S结构。在DFSClient从DataNode节点上读取数据的过程中,比较中的几个数据类是FSDataInputStream、DFSDataInputStream、DFSinputStream。下面为HDFS中read的过程:
1、文件系统读操作read,获取的类型对象为FSDataInputStream,通过fs.open操作获得,对于具体的HDFS文件系统,则采用的是通过DFSDataInputStream进行具体操作,每次获取一个DFSDataInputStream时,通过包含DFSInputStream 来进行具体的HDFS文件系统读read操作。
2、通过建立DFSInputStream对象,采用预取的方式,从NameNode中获得多个文件块的位置信息(LocatedBlocks),然后在DFSClient端缓存这些Block位置信息
3、对于DFSclient发出的每次read操作,统一转换成的格式为read(byte[ ] buffer, int offset, int len)这种DFSInputStream对象的系统调用,在read操作中比较重要的两个方法为blockSeekTo(long pos)和readBuffer(byte[ ] buffer, int offset, int len),所有具体的操作都转换成这两种操作。
4、方法blockSeekTo(long pos)作用是定位到具体的DataNode上去,同时建立具体的BlockReader对象,因为HDFS采用了ShortCircuitRead的优化措施,所以对于本地的DataNode节点上的读取操作则创建的为BlockReaderLocal对象,而对于远端的DataNode则建立RemoteBlockReader对象进行接下来的操作。接下来重点介绍的是RemoteBlockReader对象与DataNode的交互过程。
5、在建立具体的BlockReader对象之后,通过readbuffer操作,使用blockreader对象的read方法进行数据的读取操作。
下面是RemoteBlockReader建立与DataNode之间的通信过程:
RemoteBlockReader与DataNode之间通信的协议过程:
1、RemoteBlockReader端的通信:
a、RemoteBlockReader请求通信,通信头部格式如下:
+------------------------------------------------------+
| 2 byte DATA_TRANSFER_VERSION |
+------------------------------------------------------+
| 1 byte OP_READ_BLOCK |
+------------------------------------------------------+
| 8 byte BLOCK_ID |
+------------------------------------------------------+
| 8 byte BLOCK_GENERATION_STAMP |
+------------------------------------------------------+
| 8 byte startOffset |
+------------------------------------------------------+
| 8 byte length |
+------------------------------------------------------+
| text client_name |
+------------------------------------------------------+
| accessToken |
+------------------------------------------------------+
b、DataNode受到RemoteBlockReader的读取数据请求之后,发送响应信息,如果成功,则为OP_STATUS_SUCCESS。
c、接下来就是获取相应的检验和的类型checksum,同时在本地建立相应的checksum。DataNode返回具体的firstChunkOffset信息。
d、执行具体的read(byte[ ] buffer, int offset, int len)时,使用的是RemoteBlockReader的借口FSInputChecker里面的方法,通过检查,如果发现相应的buffer里面的data不存在的时候,则执行具体的readchunk操作,从DataNode中的BlockSender中读取相应的chunk数据信息,同时读取检验和,DataNode使用的是packet数据格式发送数据,在RemoteBlockReader读取数据后,进行checksum检查,如果发现不一致的情况,则向NameNode报告这个Block出现了问题,如果没有什么问题,则向DataNode报告检验和正常,当一个block读取完成后,DataNode上的BlockScanner就可以不用扫描这个Block,检查Block的完整信息。
2、DataNode端的通信:
a、DataNode上启动DataXceiverServer对象,用于侦听来自DFSclient或者其他DataNode上的连接请求,当有一个连接请求的时候建立一个DataXceiver对象为之服务
b、DataXceiver对象接受来自DFSClient端的请求头部,首先进行数据头部报文的识别。如果不是DATA_TRANSFER_VERSION类型,报告类型不匹配错误。然后读取DFSClient发送的读取的操作类型信息,如果是OP_READ_BLOCK,则执行readBlock()。
c、在DataNode端建立具体的BlockSender类型对象,这个对象用于具体的数据block发送操作,在DataNode端,从disk上读读取整个Block内核中去。在BlockSender中读取Block中使用了内核优化措施,调用NativeIO方法。在发送数据的时候也使用了内核优化措施,不用经过内核态-》用户态-》内核态的过程,有效的减少了时间。
以上大致就是HDFS中涉及到的Read过程。