HDFS通信协议(一)

Hadoop RPC接口

HDFS通信协议抽象了HDFS各个节点之间的调用接口,这一篇文章只是简要介绍下HDFS RPC有哪些接口,具体有下面文章一一概述
Hadoop RPC调用时基于Protobuf实现的。Hadoop RPC接口主要定义在org.apache.hadoop.hdfs.protocol包和org.apache.hadoop.hdfs.server.protocol包中,它包括以下几个接口:

1.ClientProctol

这个接口定义了客户端与名字节点间的接口,这个接口定义的方法有很多,客户端对文件系统的所有操作都需要通过这个接口,客户端的读写文件操作也需要通过这个接口与NameNode协商之后,再进行数据块的读写操作。ClientProtocol接口有大约80多个方法,我们可以把这个接口分为如下几类:
-HDFS文件读相关的操作。
-HDFS文件写与追写的相关操作。
-管理HDFS命名空间的相关操作。
-系统问题与管理相关的操作。
-快照相关的操作。
-缓存相关的操作。
-其他操作。
前三部分都可以在FileSystem类中找到对应的方法,这些方法都是用来支持FileSystem实现的。对于系统问题与管理相关的操作,则是由DFSAdmin这个工具类发起的,其中的方法是用于支持管理员配置和管理HDFS的。在讲解HDFS读取文件之前先介绍几个函数,如下:
1)读数据相关方法
-getBlockLocations()
客户端会调用ClientBlockLocations()方法获取HDFS文件制定范围内所有数据块的位置信息。这个方法的参数是HDFS文件的文件名以及读取范围,返回值是文件指定范围内所有数据块的文件名以及他们的位置信息,使用LocatedBlocks对象封装。每个数据块的位置信息指的是存储这个数据块副本的所有Datanode的信息,这些Datanode会以与当前Client的距离远近排序。Client读取数据时,会首先调用getBlockLocations()方法获取HDFS文件的所有数据块的位置信息,然后客户端会根据这些位置信息从DataNode读取数据块。getBlockLocations()定义如下:
//src为文件名,offset为文件偏移量 length为文件长度
//LocatedBlocks封装了文件名和数据块所在DataNode信息
public LocatedBlocks getBlockLocations(String src,long offset,
long length)

- reportBadBlocks()
客户端会调用ClientProtocol.reportBadBlocks()方法向NameNode汇报错误的数据块。当客户端读取客户端读取数据出错时,DFSInputStream会切换到另一个保存了这个数据块副本的DataNode,然后读取数据。同时需要注意,数据块的应答包中不仅包含了数据,还包含了校验值。HDFS客户端收到数据应答包时,会对数据进行检查,如果出现校验错误,也就是数据节点上得这个数据块副本出现了损坏,HDFS客户端就会通过ClientProtocol.reportBadBlock()向NameNode汇报这个损坏的数据块副本,同时DFSInputStream会尝试从其他的DataNode读取这个数据块。

HDFS客户端读流程

这里写图片描述
步骤一:打开HDFS文件:HDFS客户端首先调用DistributedFileSystem.open()方法(这里需要注意DistributedFileSystem并不是FileSystem,具体详见http://blog.youkuaiyun.com/xhh198781/article/details/6915211) 打开HDFS文件,这个方法在底层会调用ClientProtocol.open()方法,该方法会返回一个HdfsDataInputStream对象用于读取数据块。HdfsDataInputStream其实是一个DFSInputStream的装饰类,真正进行数据块读取操作的是DFSInputStream对象。
步骤2:从NameNode获取DataNode地址:在 DFSInputStream的构造方法中,会调用ClientProtocol.getBlockLocations()方法向NameNode获取HDFS文件起始位置数据块的位置信息。NameNode返回的数据块的存储位置是按照与客户端的距离远近排序的(使用LocatedBlock对象存储),所以DFSInputStream可以选择一个最优的DataNode节点,然后与这个DataNode节点建立数据连接读取数据块。
步骤3:连接到DataNode读取数据块:HDFSClient通过调用DFSInputStream.read()方法从这个最优的DataNode读取数据块,数据会以数据包为单位从数据节点通过流式接口传送到客户端。当达到一个数据块的末尾时,DFSInputStream就会再次调用ClientProtocol.getBlockLocations()获取文件下一个数据块的位置信息,并建立新的数据块的最优节点之间的连接,然后HDFS客户端就可以继续读取数据块了。
步骤4:关闭输入流:当客户端成功完成文件读取后,会通过HdfsDataInputStream.close()方法关闭输入流。

2)写/追加写数据相关方法

在介绍客户端写流程之前我们先介绍一些ClientProtocol中8个重要的方法:
- create():用于在HDFS的文件系统目录中创建一个空的文件,创建的路径由src参数指定。这个空文件创建后对于其他的客户端是“可读”的,但是这些客户端不能删除,重命名或者移动这个文件,直到这个文件被关闭或者租约过期。
public HdfsFileStatus create(String src,FsPermission masked,String client,EnumSetWritable<CreateFlag> flag,boolean createParent,short replication,long blockSize,CryptoProtocolVersion[] supportedVersion)
- addBlock():创建完空文件之后,则会调用addBlock()方法获取存储文件数据的数据块的位置信息。获得数据块之后可以建立数据管道流,进行写操作。
- append():用于打开一个已有的文件,如果这个文件的最后一个数据块没有写完,则返回这个数据块的位置信息(使用LocatedBlocks对象封装),若最后一个数据块已经满了,则还是调用addBlock()对数据进行写操作。下面我们来看看这个函数:
//previous参数是上一个数据块的引用
//excludeNodes参数则是数据节点的黑名单,保存了客户端无法连接的一些数据节点,建议NameNode在分配保存数据副本的数据节点时不要考虑这些DataNode。
//favoredNodes参数则是客户端所希望的保存数据块副本的数据节点的列表。
public LoacatedBlock addBlock(String src,String client,ExtendedBlock previous,DatanodeInof[] excludeNodes,long fileId,String[] favoredNode)

这里需要强调的是,调用addBlock()方法要传入上一个数据块的引用。
- complete():当客户端完成了整个文件的写入操作后,会调用complete()方法通知NameNode。这个操作会提交新写入HDFS文件的所有数据块,当这些数据的副本数量满足系统配置最小副本系数,complete()方法会返回true,这时Namenode中文件的状态也会构建状态转换为正常状态;否则会返回false,客户端就需要重复调用complete()操作,直到该方法返回true。

上述几个方法都是写正常情况下必须调用的,下面我们来说说几个写操作异常时需要调用的函数。
- abandonBlock():该方法放弃一个新申请的数据块。当客户端获取一个新申请的数据块,发现无法建立到该数据块的连接时,会调用abandonBlock()方法通知Namenode放弃这个数据块(这时,客户端会再次调用addBlock()方法获取新的数据块,并在传入参数时将无法连接的数据节点放入excludeNode参数列表中,避免下次Namenode将数据块副本再次分配到该DataNode上 )。
- getAdditionalDataNode():abandonBlock()用于处理客户端建立数据流管道时数据节点出现故障的情况。getAdditionalDataNode()用于处理客户端已经建立数据流管道后DataNode出现错误的情况。这时客户端首先调用getAdditionDataNode()方法向Namenode申请一个 新的DataNode来代替出现故障的DataNode。
- updateBlockaForPipeline():调用getAdditionalDataNode()之后,客户对调用updateBlockaForPipeline()方法向Namenode申请为这个数据块分配新的时间戳。(使用新的时间戳建立新的数据流通道来执行写操作)
- updatePipeline():建立通道之后客户端还需要调用updatePipeline()方法更新NameNode中当前数据块的数据流管道信息。

HDFS客户端写流程

这里写图片描述
步骤一: 创建文件:HDFS客户端写一个新的文件时,会首先调用DistributedFileSystem.create()方法在HDFS文件系统中创建一个新的文件。这个方法在底层会通过ClientProtocol.create()方法通知NameNode执行对应的操作,NameNode会首先在文件系统目录树中指定路径下添加一个新的文件,然后创建新文件的操作记录到editlog中。完成ClientProtocol.create()调用后,DistributedFileSystem.create()方法就会返回一个HdfsDataOutputStream对象,这个对象底层包装了一个DFSOutputStream对象,真正执行写数据操作的是DFSOutputStream。
步骤二:建立数据流通道:获取了DFSOutputStream对象后,HDFS客户端就可以调用DFSOutputStream.write()方法来写数据了。这里需要注意,DistributedFileSystem.create()只是在文件目录树中创建了一个空文件,并没有申请任何数据块,DFSOutputStream会首先调用ClientProtocol.addBlock()向NameNode申请一个新的空数据块,addBlock()方法会返回一个LocatedBlock对象,这个对象保存了存储这个新数据块的所有数据节点的位置信息。获得了数据流管道中所有数据节点(DataNode)的信息后,DFSOutputStream就可以建立数据流管道写数据块了。
步骤三:通过数据流管道写入数据:成功建立数据流管道后,HDFS客户端就可以向数据流管道写数据了。写入DFSOutputStream中的数据会先被缓存在数据流中 ,之后这些数据会被切分成一个个数据包(packet)通过数据流管道发送到所有数据节点(DataNode)。为了减少HDFS客户端与数据节点(DataNode)的通信消耗,HDFS客户端会将数据写入一个数据节点(DataNode),数据备份的任务是通过DataNode之间进行的。这里,每个数据包(packet)都有个确认包,确认包会逆序通过数据流管道回到输出流。输出流确认了所有数据节点已经写入这个数据包之后,就会从对应的缓存队列删除这个数据包。当客户端写满一个数据块之后,会调用addBlock()申请一个新的数据块,然后循环执行上述操作。
步骤四:关闭输入流并提交文件:当HDFS客户端完成了整个文件中所有数据块的写操作之后,就可以调用close()方法关闭输出流,并调用ClientProtocol.complete()方法通知NameNode提交整个文件中的所有数据块,也就完成了整个文件得写入流程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值