DataNode数据块工作流DataXceiver

转载 2013年12月04日 22:22:07

DataXceviverServer:

监听块传输连接请求,同时控制进行的块传输请求数(同一时刻的传输数不能超过maxXceiverCount)和带宽耗费情况(块传输时带宽耗费带宽不能超过预定值BlockTransferThrottler.bytesPerPeriod)。当在run里监听到一个块传输请求时,开启一个DataXceiver线程处理块传输。系统关闭时,会关闭用于监听的连接的ServerSocket同时将DataXceiver所产生的线程关闭,使得DataXceiver因为出现错误而退出。

BlockTransferThrottler:

带宽节流器,用于协调块运输所耗费的带宽。一个块传输过程中,如果已经使用了超过预期的带宽就令其等待wait一段时间,使得不会因为某个块传输而带宽耗尽。

DataXceiver继承自runnable,用于实现块的发送和接收。在run里先进行版本的校验,再判断是否当前同时传输块的连接数超过了dataXceiverServer.maxXceiverCount值,如果是操作失败。然后从客户端读取要进行的操作op

OP_WRITE_BLOCK (80):写数据块 

OP_READ_BLOCK (81):读数据块 

OP_READ_METADATA (82):读数据块元文件 

OP_REPLACE_BLOCK (83):替换一个数据块 

OP_COPY_BLOCK (84):拷贝一个数据块 

OP_BLOCK_CHECKSUM (85):读数据块检验码 

根据这个操作调用相应的方法进行处理。处理后关闭流,关闭socket

=============================================================

读操作DataXceiver.readBlock

依次接收blockIdgenStamp块时间戳,startOffset块偏移位置,length要读取的块大小,clientName客户端请求者的名字(非空串表示这是客户端的写块操作,否则为replaceBlock中的主动读取块操作)

根据以上的信息建立一个BlockReader对象,向请求者发送OP_STATUS_SUCCESS表示操作状态,然后调用BlockReader.sendBlock将块发送给请求者。更新datanode.myMetrics.bytesReaddatanode.myMetrics.blocksRead。最后关闭输出流和blockReader

BlockReader的构造函数中由于设计到校验和,所以比较复杂。大致流程是获取元数据(校验和)文件的输入流,进行元数据版本的检验,获取校验和checksum,计算块文件和校验和文件开始读取的位置,打开块数据流。

  

BlockReader.sendBlock先发送checksum.header给请求者,然后将块文件划分成最多maxChunksPerPacket个数据包进行发送,每个数据包大小为pktSize,然后多次调研BlockReader.sendChunks(pktBuf,maxChunksPerPacket,streamForSendChunks)将数据包发送出去。发送数据包结束后发送一个0表示块的发送完成。然后BlockReader.close()关闭各种资源。在发送包的过程中会统计已发送的流量。



BlockReader.sendChunks(ByteBuffer pkt, int maxChunks, OutputStream out)中先计算一个数据包能发送的最多个chunks,然后将packetLen数据包长度、offset数据包开始位置在块中的偏移、seqno数据包的编号、是不是最后一个数据包写到pkt,然后将块元数据文件中的校验数据读出到pkt中。计算出块数据在pkt中的位置(存放在校验数据之后)。然后根据BlockReader.blockInPosition变量值判断是否可以用FileChannel将数据发给块请求者。如果不可以,就将块数据读取到buf中,然后利用checksum对读出来的数据生成校验和,并与先前读出的块元数据文件进行校验,校验成功后将buf发送给请求者。如果可以,就先将buf中的校验和部分发送出去,然后再利用fileChannel将块文件中的数据发送出去。最后调用BlockTransferThrottler.throttle进行节流控制。

【注:BlockReader.blockInPosition是用于判断是否可以打开一个fileChannel,如果可以就根据这个fileChannel.transferTo方法将数据包发送到客户端。】

=============================================================

写操作DataXceiver.writeBlock

写数据块要涉及到对数据块进行备份,即要将块写到多个Datanodes。会将这些Datanodes组织成一个pipeline

client发送块给pipeline上的第一个datanode,此datanode将块数据写到本地并传给下一个。然后将后续块返回来的响应信息加上自身的操作结果信息一起返回到前面。在pipeline上,如果某个DataNode有后续节点,那么,它必须等到后续节点的成功应答,才可以发送应答到它前面的节点。

DataXceiver.writeBlock会先创建三个数据流mirrorOutmirrorInreplyOut,到下一个DNsocket,以及用于接收块和写块的BlockReceiver

DataOutputStream mirrorOut  // stream to next target

DataInputStream mirrorIn    // reply from next target

DataOutputStream replyOut   // stream to prev target

Socket mirrorSock           // socket to next target

BlockReceiver blockReceiver // responsible for data handling


然后调用 blockReceiver.receiveBlock进行块的操作。操作完后关闭流和socket

   

BlockReceiver.receiveBlock会先start一个PacketResponder线程,然后一直调用receivePacket直到读完该块数据为止。然后往下一个节点写入0表示块写入完成。等待到packets的所有响应都发送完毕。调用FSDataset进行finalizeBlock。最后关闭PacketResponder线程。

BlockReceiver.receivePacket调用BlockReceiver.readNextPacket接收一个packet(最终是调用readToBufpacket读到buf)。然后将packet写到下一个节点(在将数据都写给下一个节点后,写入一个0表示块结束)。一个packet格式如下图:

只有pipeline最后一个节点或发送端是namenode时才需要对发过来的数据进行校验verifyChunks。接着会把块数据和块元数据文件写入到tmp目录下。本地写入完成后向responder.ackQueue入队一个响应,以便向上一个DNclient响应块操作。

pipeline上的DataNode都有个BlockReceiver.PacketResponder线程,用于向上一个datanode或客户端发送响应消息以及心跳保活。

pipeline上的心跳保活机制是由后往前进行的:最后一个DataNode D 发给上一个DataNode CC接收到发现是一个心跳消息,就像B发送一个心跳...

PacketResponder.run从下一个DN中读取一个PipelineAck,得到packetseqno(可以是心跳标识、错误标志、packet标识)。如果是心跳就将这个ack回送到上一个DN。如果是packet号,就先从ackQueue读出一个Packet,验证这个Packet.seqno是否与之前读到的ack.seqno相等(表示是同一个块),如果这个数据包是块中最后一个,就用notifyNamenodeReceivedBlocknamenode发送通知【所有改发DataNode的操作,需要把信息更新刡NameNode上】,同时调用finalizeBlock将块和元数据文件移到到current下。然后从之前读出的ack.replies加上当前的操作状态OP_STATUS_SUCCESS,包装成一个PipelineAck,写回到上一个DNclient

上一个DNclient发送过来的数据包操作成功后,通过将Packet(seqno,lastPacketInBlock)加入到PacketResponder.ackQueue中,PacketResponderackQueue中取出Packet,得知要对pipeline的写数据包的情况向上一个DNclient通报。根据从下一个DN得到的后续DN的写数据包响应消息,向上一个DNclient发送一个PipelineAck

datanode接收到的是最后一个packet时,在PacketResponder.run内调用finalizeBlockBlock移到current下,并调用notifyNamenodeReceivedBlock(block,DataNode.EMPTY_DEL_HINT);namenode通知块已写完。 

===============================================================

读元数据文件操作DataXceiver.readMetadata


先根据请求者传过来的blockIdgenStamp找到相应的块信息,然后获得块元数据文件输入流,将元数据读取到buf中,建立一个到请求者的输出流,依次写入OP_STATUS_SUCCESS、元数据文件大小、元数据。最后关闭流。

=============================================================

复制块文件操作DataXceiver.copyBlock

调用BlockSender.sendBlock将块发送出去。

BlockSender构造函数:

BlockSender(Block block, long startOffset, long length,boolean corruptChecksumOk, boolean chunkOffsetOK,boolean verifyChecksum, DataNode datanode);

readBlock中:

new BlockSender(block, startOffset, length,true, true, false, datanode, clientTraceFmt);

copyBlock中:


new BlockSender(block, 0, -1, false, false, false,datanode);

读块是从块指定位置开始读特定长度,而copyBlock是整个块进行复制。

=============================================================

块文件的替换操作DataXceiver.replaceBlock

替换块只发生在指定的一个DataNode上,而writeBlock是在一个pipeline上。

替换块的流程大致是:得到替换块请求者的信息,向请求者发送复制块请求OP_COPY_BLOCK,创建并调用BlockReceiver.receiveBlock用于从请求者那读取块,然后向namenode发送一个块变更通知,更新namenode上的块信息。


格式对比:

BlockReceiver(Block block, DataInputStream in, String inAddr,String myAddr, boolean isRecovery, String clientName,DatanodeInfo srcDataNode, DataNode datanode);

replaceBlock中的创建BlockReceiver的参数:

new BlockReceiver(block, proxyReply, proxySock.getRemoteSocketAddress().toString(),proxySock.getLocalSocketAddress().toString(),false, "", null, datanode);

writeBlock中的创建BlockReceiver的参数:

new BlockReceiver(block, in,s.getRemoteSocketAddress().toString(),s.getLocalSocketAddress().toString(),isRecovery, client, srcDataNode, datanode);

proxyReplyin 不一样,返是因为发起请求的节点和提供数据的节点并不是同一个。写数据块发起请求方也提供数据,

替换数据块请求方不提供数据,而是提供了一个数据源(proxySource参数),由replaceBlock发起一个拷贝数据块的请求,

建立数据源。对亍拷贝数据块操作,isRecovery=falseclient="", srcDataNode=nullclient=""表示这不是客户端的写块。

块被替换后,要将被替换掉的块从namenode中删除,这是通过datanode.notifyNamenodeReceivedBlock(block, sourceID)实现的。

=============================================================

读取块校验操作DataXceiver.getBlockChecksum


流程:根据块元数据文件中的数据进行利用MD5算法计算校验和,然后将校验和发送出去。


原文参考:http://blog.csdn.net/cklsoft/article/details/8644514

Hadoop--Datanode存储均衡问题

今天通过jconsole监控Hadoop写数据,发现一个问题,datanode数据存储不均衡。   环境部署情况:我们多台服务器,其中4台server用来作为DataNode,并且其中3台部署了web...
  • u010926176
  • u010926176
  • 2013年10月09日 15:35
  • 3944

HDFS源码分析(5):datanode数据块的读与写DataXceiver

前提 Hadoop版本:hadoop-0.20.2 概述 现在已经知道datanode是通过DataXceiver来处理客户端和其它datanode的请求,在分析DataXceive...
  • linuxheik
  • linuxheik
  • 2016年04月16日 12:49
  • 246

hadoop datanode 磁盘坏掉之后的解决办法

之前发了一篇文章: http://blog.csdn.net/lxpbs8851/article/details/17241551 记录的是由于开启了 坏掉磁盘的datanode,导致集群部分功能无法...
  • lxpbs8851
  • lxpbs8851
  • 2013年12月23日 14:18
  • 7431

datanode数据存放位置研究

DataNode细节研究:   Datanode中数据实际存放位置: 自定义路径+dfs/data/current/BP-190247797-192.168.10.220-1460040893538/...
  • xiaoshunzi111
  • xiaoshunzi111
  • 2016年04月25日 08:36
  • 1361

HDFS内副本和块的状态分析

一 数据块(Block)和数据块副本(Replica)关系 Block是HDFS数据块的基本抽象,实现可Writable接口,可以序列化。 是NameNode数据块的表示。   Replica是...
  • zhanglh046
  • zhanglh046
  • 2017年11月16日 09:34
  • 122

HDFS源码分析DataXceiver之读数据块

在《HDFS源码分析DataXceiver之整体流程》一文中我们知道,无论来自客户端还是其他数据节点的请求达到DataNode时,DataNode上的后台线程DataXceiverServer均为每个...
  • lipeng_bigdata
  • lipeng_bigdata
  • 2016年03月12日 16:23
  • 913

HDFS缓存机制

前言 缓存,英文单词译为Cache,缓存可以帮助我们干很多事,当然最直接的体会就是可以减少不必要的数据请求和操作.同样在HDFS中,也存在着一套完整的缓存机制,但可能使用了解此机制的人并不多,因...
  • Androidlushangderen
  • Androidlushangderen
  • 2016年03月13日 14:41
  • 4924

DataNode迁移方案

DataNode迁移方案目标由于外界因素的影响,需要将原有dn所在节点的机器从A机房换到B机房,其中会涉及到主机名和IP的改变.最终的目标是迁移之后对集群不造成大影响, 服务依然可用,数据不发生丢失...
  • Androidlushangderen
  • Androidlushangderen
  • 2016年01月08日 17:44
  • 2736

DataNode引用计数磁盘选择策略

前言在HDFS中,所有的数据都是存在各个DataNode上的.而这些DataNode上的数据都是存放于节点机器上的各个目录中的,而一般每个目录我们会对应到1个独立的盘,以便我们把机器的存储空间基本用上...
  • Androidlushangderen
  • Androidlushangderen
  • 2016年01月17日 15:55
  • 3708

DataNode写数据

client发送块给pipeline上的第一个datanode,此datanode将块数据写到本地并传给下一个。然后将后续块返回来的响应信息加上自身的操作结果信息一起返回到前面。在pipeline上,...
  • yueqian_zhu
  • yueqian_zhu
  • 2014年07月03日 16:25
  • 635
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:DataNode数据块工作流DataXceiver
举报原因:
原因补充:

(最多只允许输入30个字)