HDFS资料整理Word文件下载.docx
《HDFS资料整理Word文件下载.docx》由会员分享,可在线阅读,更多相关《HDFS资料整理Word文件下载.docx(16页珍藏版)》请在冰豆网上搜索。
FileSystem返回FSDataOutputStream给client。
FSDataOutputStream封装了一个DFSOutputStream来处理与datanodes和namenode之间的通讯。
3.当client写一个block数据的时候,DFSOutputStream把数据分成很多packet。
FSDataOutputStream询问namenode挑选存储这个block以及它的副本的datanode列表。
这个datanode列表组成了一个管道,在上图管道由三个datanode组成(备份参数是3),这三个datanode的选择有一定的副本放置策略,详细请看下一篇。
4.FSDataOutputStream把packet写进管道的第一个datanode,然后管道把packet转发给第二个datanode,这样一直转发到最后一个datanode。
5.只有当管道里所有datanode都返回写入成功,这个packet才算写成功,发送应答给FSDataOutputStream。
开始下一个packet。
如果某个datanode写失败了,会产生如下步骤,但是这些对client是透明的。
1)管道关闭。
2)正常的datanode上正在写的block会有一个新ID(需要和namenode通信)。
这样失败的datanode上的那个不完整的block在上报心跳的时候会被删掉。
3)失败的datanode会被移出管道。
block中剩余的packet继续写入管道的其他两个datanode
4)namenode会标记这个block的副本个数少于指定值。
block的副本会稍后在另一个datanode创建。
5)有些时候多个datanode会失败。
只要dfs.replication.min(缺省是1)个datanode成功了,整个写入过程就算成功。
缺少的副本会异步的恢复。
6.当client完成了写所有block的数据后,调用FSDataOutputStream的close方法关闭文件。
7.FSDataOutputStream通知namenode写文件结束。
三、HDFS文件创建流程
文件夹的创建是一个相对简单的过程,主要是通过FileSystem中的mkdirs()方法,这个方法在DFSClient实例中调用同名方法mkdirs(),通过Hadoop本身的RPC机制调用Namenode的mkdirs()方法,最终这个调用PUSH到FSNameSystem的mkdirsInternal方法,这个方法主要就是检验访问权限,最后通过FSDirectory的unprotectedMkdir()方法,构建一个INodeDirectory实例添加到文件系统的目录树中。
文件节点的创建与添加相对比较麻烦,主要步骤如下:
FileSystem的create方法返回一个很重要的类FSDataOutputStream,这一点也比较好理解,就像java中的文件流一样,创建一个文件写入流对文件内容进行追加,首先我们看文件创建阶段namenode主要做了什么事情(权限验证以及租约验证这些前面都已经有提到,下面的内容就会掠过这一部分)
DfSOutputStream在实例构建时,通过Hadoop本身的RPC机制调用Namenode的create方法,最终这个调用PUSH到FSNameSystem的StartFileInternal方法,需要做权限验证,租约检验等工作,这个方法主要作用就是创建一个INodeFileUnderConstruction实例(上面已经提过,文件写入过程中都会有一个INodeFileUnderConstruction与这个文件对应),这个实例最后通过FSDirectoty的addNode()方法添加到文件系统目录数中,这个时候文件创建操作就算完成了重要的第一步,文件系统中已经有了这个文件的记录。
下面就涉及到文件的写入操作(相当复杂的部分)
这个时候就需要用到返回的DfSOutputStream对象。
这部分太复杂了,我们先分析一些基本模块儿,逐步吃透这部分的实现。
整个分布式文件系统中网络通讯部分分为两类:
1.命令类调用(这部分通过HADOOP的RPC机制进行支持)
2.流式数据传输(这部分通过HADOOP的流式数据传输协议支持)
为了保证数据的正确性,hadoop在多个关键处理单元做了数据检验操作,在流式数据网络传输部分通过校验和保证数据传输正常。
Client在DfSOutputStream对象调用write方法时,系统并不会马上把数据写入SOCKET中,而是逐个构建Package并将这些Package加入一个队列。
在DfSOutputStream对象构建时,系统通过Hadoop本身的RPC机制调用Namenode的create方法后,会启动一个后台线程streamer.start();
这个线程的主要目的就是将上述的package队列写入SOCKET中。
右图为DfSOutputStream的继承关系
其中FSOutputSummer这个类其实质是一个decorator设计模式的实现,主要的目的就是在OutputStream的voidwrite(byteb[],intoff,intlen)方法中增加一些功能,上文已经提过,文件数据传输的同时,系统会在传输的数据中增加检验和数据,系统收到数据后对数据进行校验,保证数据传输的正确性,但是用户在对文件输出流进行操作的时候并不需要关注校验和数据,用户只需要不断的调用write方法在目标文件中追加数据。
注:
我们通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的.使用Decorator的理由是:
这些功能需要由用户动态决定加入的方式和时机.Decorator提供了"
即插即用"
的方法,在运行期间决定何时增加何种功能.
我们看一下FSOutputSummer中write(byteb[],intoff,intlen)的实现
publicsynchronizedvoidwrite(byteb[],intoff,intlen)throwsIOException
{
if(off<
0||len<
0||off>
b.length-len){
thrownewArrayIndexOutOfBoundsException();
}
for(intn=0;
n<
len;
n+=write1(b,off+n,len-n)){}
}
可以看到不断调用write1方法,保证数据发送的完整性。
那么write1方法又做了什么事情呢,write1将用户需要写入的数据流首先写到自己的BUFFER中,达到一定数量(基本是一个chunk的大小)后进行CheckSum方法调用得到一段数据的校验和,然后通过writeChecksumChunk这个方法将数据以及该部分数据的校验和,按照一定格式要求一并写入Stream。
writeChecksumChunk这个方法的主要作用就是将用户写入的数据以及该部分数据的校验和做为参数调用writeChunk()方法,这个方法是一个虚方法,真正的实现在DFSOutputStream这个类中,这也合情合理,本身FSOutputSummer这个类的作用仅仅是在输出流中增加校验和数据,至于数据是如何进行传输的是通过DFSOutputStream来实现的。
那么接下来需要说明的就是DFSOutputStream的writeChunk这个方法了。
HDFS流式数据网络传输的基本单位有哪些呢?
chunk->
package->
block
我们上文已经提过:
等用户写入的数据达到一定数量(基本是一个chunk的大小)后就会对这段数据取校验和。
一定数量的chunk就会组成一个package,这个package就是最终进行网络传输的基本单元,datanode收到package后,将这些package组合起来最终得到一个block。
我们接下来通过实际主要的代码了解这部分功能的实现:
currentPacket这个对象初始化的时候就是null,第一次写入数据时这个判断成立
if(currentPacket==null)
currentPacket=newPacket(packetSize,chunksPerPacket,
bytesCurBlock);
...
//下面开始构建package包。
//在package包中增加一个chunk,首先添加这个chunk所包含数据的checksum
currentPacket.writeChecksum(checksum,0,cklen);
currentPacket.writeData(b,offset,len);
//然后添加这个chunk所包含的数据
currentPacket.numChunks++;
//增加这个package所包含的chunk个数
bytesCurBlock+=len;
//当前已经写入的byte个数
//Ifpacketisfull,enqueueitfortransmission
//如果这个package已经达到一定的chunk数量,准备实际的传输操作
if(currentPacket.numChunks==currentPacket.maxChunks||bytesCurBlock==blockSize)
......
if(bytesCurBlock==blockSize)//如果用户写入的数据,已经达到一个block缺省大小(64M)
//设置当前的package是某一个block的最后一个package
currentPacket.lastPacketInBlock=true;
//清除一些变量的值
bytesCurBlock=0;
lastFlushOffset=-1;
//这三段代码是关键的一部分代码,将已经构建完成的package写入一个dataQueue队列,由另一个线程(就是我们开始提到的:
启动一个后台线程streamer.start();
这个线程的主要目的就是将上述的package队列写入SOCKET中)从该队列中不断取出package,进行实际的网络传输
dataQueue.addLast(currentPacket);
//产生event,进而通知并唤醒等待线程
dataQueue.notifyAll();
//这一步也很重要,设置currentPacket为空,表示这个package已经满了,需要new一个新的package继续接收用户后面进一步需要写入的数据。
currentPacket=null;
//Ifthiswasthefirstwriteafterreopeningafile,then
//theabovewritefilledupanypartialchunk.Tellthesummertogeneratefull
//crcchunksfromnowon.
if(appendChunk)
appendChunk=false;
resetChecksumChunk(bytesPerChecksum);
intpsize=Math.min((int)(blockSize-bytesCurBlock),writePacketSize);
computePacketChunkSize(psize,bytesPerChecksum);
computePacketChunkSize这个方法的主要作用是计算两个参数:
1.chunksPerPacket
接下来的package需要承载多少个chunk;
因为最后一个package承载的chunk个数与文件大小也有关系。
2.packetSize
接下来的package的大小。
以上两个参数与判断是否需要new一个新的PACKAGE很有关系。
privatevoidcomputePacketChunkSize(intpsize,intcsize)
intchunkSize=csize+checksum.getChecksumSize();
intn=DataNode.PKT_HEADER_LEN+SIZE_OF_INTEGER;
chunksPerPacket=Math.max((psize-n+chunkSize-1)/chunkSize,1);
packetSize=n+chunkSize*chunksPerPacket;
if(LOG.isDebugEnabled())
}
可以看到构建的package不断添加到dataQueue这个队列,streamer.start()这个线程从中弹出package进行实际网络传输操作。
下面就涉及到比较复杂的网络传输协议部分。
我们先看一下这部分的流程:
1.上面已经讲过,开始的一步就是客户端调用create方法,在namenode上的目录树中注册一个INodeFileUnderConstruction节点,并得到一个DfSOutputStream。
2.用户得到这个outputStream后就可以进行写入操作,用户写入的数据就不断构建成package写入dataQueue这个队列。
3.streamer.start()这个线程从dataQueue队列中取出package进行实际网络传输操作。
下面的网络传输流程为关键流程:
4.streamer是一个DataStreamer的实例,这是一个线程实例。
大家知道HDFS中的文件数据会分成很多64M大小的block,所以在HDFS中保存文件数据第一步就是在namenode上申请一个特殊的blockID(当然还是通过RPC调用的方式)。
右图为文件写入流程
四、HDFS文件数据流写入传输协议
FileSystem是一个提供给用户对文件系统进行访问的抽象类,访问HDFS的具体实现类为FileSystem,用户对HDFS的所有操作就是通过这个类的具体实例完成的。
在HDFS写入文件之前需要首先在Namenode创建并注册一个INodeFileUnderConstruction,在向文件进行数据追加时,会针对这一文件逐个向系统中追加BLOCK,如果是第一次写入(或者上一个BLOCK已经写满),需要在Namenode中注册一个新的BLOCK,通过RPC调用Namenode的具体实例返回LocatedBlock对象,除了新申请的block这个LocatedBlock对象还包含多个datanodeinfo信息,表示这个block块儿需要传输的哪些datanode上进行保存。
下面我们详细看一下数据传输的具体流程以及传输协议的详细内容:
第一步将返回的多个datanode逐个建立链接形成数据传输链,如下图所示(以三份数据拷贝为例):
当流程到达第6步的时候这个管道才算建立成功,每一步都是与下一个节点建立链接,发送管道创建协议包。
管道建立协议:
4字节数据传输协议版本号(0.19.1版本的hadoop这个字段为14)
4字节操作码(追加文件时为:
OP_WRITE_BLOCK)
8字节Block的BLOCKID字段
8字节Block的时间戳字段
4字节建立的传输链中一共有多少台datanode
Boolean表示是否为恢复数据请求(recoverblock操作)
TextClient名称字串,格式为字串长度(这个采取了压缩形式)+字串内容
Boolean表示是否传输客户端信息
DatanodeInfo如果上面字段为true,才会有该字段,表示client信息
Int表示数据链中后续节点个数(每过一个节点,这个字段就减少一)
DatanodeInfo[]表示数据链中后续节点信息,上面字节是多少就读多少个
DatanodeInfo信息,传给后面的节点做下一节点的链接
ChecksumbyteChecksum的类型,可以根据该字段实例化具体checksum类
Int对多少字节进行取checksum操作
Client发出上面的请求后就等待应答,应答的协议格式如下:
返回就是一个字串信息,如果字串为空表示后续节点所有链路都链接成功,如果不为空该字段保存的就是出错的datanode信息,格式如下:
name:
port。
针对于一个block传输操作创建数据链路成功以后,就可以进行实际数据传输了。
后面的数据就是一个接一个的Package包。
4字节Package数据长度
4字节package在这个block中的偏移量
8字节一个序列号
Byte这个package是否是这个block中最后一个包
4字节实际的数据长度,除去checksum。
CheckSum数据缺省的CheckSum为CRC32,(缺省每个chunk的checksum占四个字节)
实际block数据有多少写多少
数据传输过程中一定需要耗时,如何知道数据传输链中的datanode都是正常工作的呢?
以及datanode成功收到package包以后是如何应答吗?
这部分协议相关字段如下:
如果收到下一个datanode返回4字节(-1),表示心跳正常
4字节opCode(-1,表示心跳正常)
如果收到下一个datanode返回4字节(不等于-1,也不等于-2)
4字节packageID(向上一级返回接受到的packageID)
4字节OP_STATUS_SUCCESS=0;
OP_STATUS_ERROR=1;
0表示接受成功,1表示接受失败。
这个字节根据数据链中的位置可能会收到多个,详细见下图:
client最终可以根据返回的PackageID后的多个状态字节得知哪台机器可能出现问题,从上面的流程可以看到:
datanode3可能已经写入成功,datanode2由于没有收到datanode3的应答,故而认为datanode3接受package失败,这个状态一直透传到client。
也有可能datanode3节点的确失效,datanode2也会收不到datanode3的应答响应。
DFSClient从Namenode取得需要读取的文件对应的LocatedBlocks信息以后,就会按照block的顺序与datanode建立链接并发送读取block数据的请求。
我们看一下这部分的协议格式:
4字节
数据传输协议版本号(0.19.1版本的hadoop这个字段为14)
操作码(读取文件时为:
OP_READ_BLOCK,对应81)
8字节
Block的BLOCKID字段
Block的时间戳字段
读取在block数据文件开始的偏移量
一共读取多少字节
Text
Client名称字串,格式为字串长度(这个采取了压缩形式)+字串内容
DFSClient发送读取数据块儿请求完成以后,首先等待datanode的应答,datanode应答协议格式如下:
OP_STATUS_SUCCESS=0表示链接建立成功OP_STATUS_ERROR=1表示链接建立失败
接下来Datanode就开始进行数据传送,具体数据格式如下:
数据校验相关信息ByteChunksum类型
IntbytesPerChecksum每次checksum对应的字节数
8字节Offset(读取一个block开始的偏移量)
(这8字节有些情况没有)1.DFSClient从Datanode读取block数据时,这个字段是必须有的。
2.Datanode之间进行block互相拷贝时(balance需要,或者block
块儿没有达到副本个数要求),这个字段是不存在的。
4字节packetLen,package长度
8字节chunk的偏移量(用戶读取文件时,很可能偏移一个位置,偏移的位置很
可能不在一个chunk的结束位置,因为checksum是按照chunk来计算
的,所以hadoop会将这部分偏移量的数据多传送给client,client需
要将这部分数据丢弃)
8字节Seqno序列号
Byte是否是block数据块的最后一个package
4字节传输的block中包含的数据大小
CheckSum数据缺省的CheckSum为CRC32,(缺省每个chunk的checksum占四个字节)
五、HDFS的Java访问接口
得到filesystem的实例,有两个静态方法可以得到filesystem接口的实例
publicstaticFileSystemget(Configurationconf)throwsIOException
publicstaticFileSystemget(URIuri,Configurationco