04. HDFS RPC接口之DatanodeProtocol

DatanodeProtocol是Datanode与Namenode间的接口,Datanode会使用这个接口与Namenode握手、注册、发送心跳、进行全量及增量数据块汇报。Namenode会在Datanode的心跳响应中携带Namenode指令,Datanode收到Namenode指令后会执行对应的操作。注意,Namenode向Datanode下发Namenode指令是没有其他任何接口的,只会通过DatanodeProtocol的返回值来下发命令

DatanodeProtocol定法的方法如下图所示

 
13274599-9d2f22d59c033865.png
2. DatanodeProtocol方法.png

这些方法可以分为三种类型,Datanode启动相关、心跳相关及数据块读写相关

Datanode启动相关方法

一个完整的Datanode启动操作会与Namenode进行4次交互,也就是调用4次DatanodeProtocol定义的方法。首先调用versionRequest()与Namenode进行握手操作,然后调用registerDatanode()向Namenode注册当前的Datanode,接着调用blockReport()汇报Datanode上存储的所有数据块,最后调用cacheReport()汇报Datanode缓存的所有数据块。

首先看下versionRequest()方法,方法定义如下

public NamespaceInfo versionRequest() throws IOException;

Datanode启动会首先调用versionRequest()方法与Namenode进行握手。这个方法的返回值是一个NamespaceInfo对象,NamespaceInfo对象会封装当前HDFS集群的命名空间信息,包括存储系统的布局版本号(layoutversion)、当前的命名空间ID(namespaceId)、集群ID(clusterId)、文件系统的创建时间(ctime)、构建时的HDFS版本号(buildVersion)、块池ID(blockpoolId)、当前的软件版本号(softwareVersion)等。Datanode获取到NamespaceInfo对象后,就会比较Datanode当前的HDFS版本号和Namenode的HDFS版本号,如果Datanode版本与Namenode版本不能协同工作,则抛出异常,Datanode也就无法注册到该Namenode上。如果当前Datanode上已经有了文件存储的目录,那么Datanode还会检查Datanode存储上的块池ID、文件系统ID、以及集群ID与Namenode返回的是否一致。

成功握手后,Datanode会调用registerDatanode()方法向Namenode注册当前的Datanode,该方法定义如下

public DatanodeRegistration registerDatanode(DatanodeRegistration registration
    ) throws IOException;

这个方法的参数是一个DatanodeRegistration对象。它封装了DatanodeID、Datanode的存储系统的布局版本号(layoutversion)、当前的命名空间ID(namespaceId)、集群ID(clusterId)、文件系统的创建时间(ctime)以及Datanode当前的软件版本号(softwareVersion)。Namenode会判断Datanode的软件版本号与Namenode的软件版本号是否兼容,如果兼容则进行注册操作,并返回一个DatanodeRegistration对象供Datanode后续处理逻辑使用。

Datanode成功向Namenode注册之后,Datanode会调用blockReport()方法向Namenode上报它管理的所有数据块的信息。该方法定义如下

public DatanodeCommand blockReport(DatanodeRegistration registration,
    String poolId, StorageBlockReport[] reports) throws IOException;

这个方法需要三个参数,DatanodeRegistration用于标识当前的Datanode,poolId用于标识数据块所在的块池ID,reports是一个StorageBlockReport对象的数组,每个StorageBlockReport对象都用于记录Datanode上一个存储空间存储的数据块。注意,上报的数据块是以长整形数组保存的,每个已经提交的数据块(finalized)以3个长整型来表示,每个构建中的数据块(under-construction)以4个长整型来表示。之所以不使用ExtendedBlock对象保存上报的数据块,是因为这样可以减少blockReport()操作所使用的内存,Namenode接收到消息时,不需要创建大量的ExtendedBlock对象,只需要不断地从长整型数组中提取数据块即可。

Namenode接收到blockReport()请求之后,会根据Datanode上报的数据块存储情况建立数据块与数据节点之间的对应关系。同时,Namenode会在blockReport()的响应中携带Namenode指令,通知Datanode进行重新注册、发送心跳、备份或者删除Datanode本地磁盘上数据块副本操作。这些Namenode节点指令都是以DatanodeCommand对象封装的。

blockReport()方法只在Datanode启动时以及指定间隔时执行一次。在这里间隔是由dfs.blockreport.intervalMsec参数配置的,默认是6小时执行一次。cacheReport()方法与blockReport()方法是完全一致的,只不过汇报的是当前Datanode上缓存的书友数据块。

cacheReport()方法定义如下

public DatanodeCommand cacheReport(DatanodeRegistration registration,
    String poolId, List<Long> blockIds) throws IOException;

心跳相关方法

分布式系统的节点之间大多采用心跳来维护节点的健康状态。HDFS也是一样的,Datanode会定期(有dfs.heartbeat.interval配置项配置,默认是3秒)向Namenode发送心跳,如果Namenode长时间没有接到Datanode发送的心跳,则Namenode会认为该Datanode失效

DatanodeProtocol的sendHeartbeat()方法就是用于心跳汇报的接口。该方法定义如下

public HeartbeatResponse sendHeartbeat(DatanodeRegistration registration,
                                     StorageReport[] reports,
                                     long dnCacheCapacity,
                                     long dnCacheUsed,
                                     int xmitsInProgress,
                                     int xceiverCount,
                                     int failedVolumes) throws IOException;

该方法除了携带标识Datanode身份的DatanodeRegistration对象外,还包括数据节点上所有存储的状态、缓存的状态、正在写文件数据的连接数、读写数据使用的线程数等。

sendHeartbeat()会返回一个HeartbeatResponse对象,这个对象包含了Namenode向Datanode发送的Namenode指令,以及当前Namenode的HA状态。注意,在开启了HA的HDFS集群中,Datanode是需要同时向Active Namenode以及Standby Namenode发送心跳的,不过只有Active Namenode才能向Datanode下发Namenode指令

数据块读写相关方法

Datanode在进行数据块读写操作时与Namenode交互的方法包括reportBadBlocks()、blockReceivedAndDeleted()、commitBlockSynchronization()三个方法。

reportBadBlocks()与ClientProtocol.reportBadBlocks()方法类似,定义如下

public void reportBadBlocks(LocatedBlock[] blocks) throws IOException;

Datanode会调用这个方法向Namenode汇报损坏的数据块,Datanode会在三种情况下调用这个方法

  1. DataBlockScanner线程定期扫描数据节点上存储的数据块,发现数据块的校验出现错误时
  2. 数据流管道写数据时,Datanode接收了一个新的数据块,进行数据块校验操作出现错误时
  3. 进行数据块复制操作(DataTransfer),Datanode读取本地存储的数据块时,发现本地数据块副本的长度小于Namenode记录的长度,则认为该块数据已经无效

reportBadBlocks()方法的参数是LocatedBlock对象,这个对象描述了出现错误数据块的位置,Namenode收到reportBadBlocks()请求后,会下发数据块副本删除指定删除错误的数据块。

Datanode会定期(默认是5分钟,不可以配置)调用blockReceivedAndDeleted()方法向Namenode汇报Datanode新接受的数据块或者删除的数据块。blockReceivedAndDeleted()方法定义如下

public void blockReceivedAndDeleted(DatanodeRegistration registration,
                          String poolId,
                          StorageReceivedDeletedBlocks[] rcvdAndDeletedBlocks)
                          throws IOException;

Datanode接收一个数据块,可能是因为Client写入了新的数据块,或者从别的Datanode上复制一个数据块到当前Datanode。

Datanode删除一个数据块,则有可能是因为该数据块的副本数量过多,Namenode向当前Datanode下发了删除数据块副本的指令。

可以把blockReceivedAndDeleted()方法理解为blockReport()的增量汇报,这个方法的参数包括DatanodeRegistration对象、增量汇报数据块所在的块池ID,以及StorageReceivedDeletedBlocks对象的数组,这里StorageReceivedDeletedBlocks对象封装了Datanode的一个数据存储上新添加以及删除的数据块集合。Namenode接收了这个请求之后,会更新它内存中数据块与数据节点的对应关系

commitBlockSynchronization()方法用于租约恢复操作时同步数据块的状态,commitBlockSynchronization()方法定义如下

public void commitBlockSynchronization(ExtendedBlock block,
    long newgenerationstamp, long newlength,
    boolean closeFile, boolean deleteblock, DatanodeID[] newtargets,
    String[] newtargetstorages) throws IOException;

在恢复租约操作时,主数据节点完成所有租约恢复协调操作后调用commitBlockSynchronization()方法同步Datanode和Namenode上所有数据块的状态。

其他方法

DatanodeProtocol中最后一个方法是errorReport(),该方法用于向Namenode上报运行过程中发生的一些状况,如磁盘不可用等,这个方法在调试时非常有用

DatanodeCommand

g sendHeartbeat()、blockReport()以及cacheReport()方法的返回值都会携带Namenode向Datanode下发的Namenode指令。在HDFS中,使用DatanodeCommand类描述Namenode向Datanode发出的Namenode指令

DatanodeCommand类及其子类结构如图

 
13274599-a5a078e663fb2026.png
3. DataNodeCommand类及其子类.png

DatanodeCommand是所有Namenode节点的基类,一共有7个子类,但在DatanodeProtocol中一共定义了10个Namenode指令,每个指令都有一个唯一的编号与之对应,代入如下

final static int DNA_UNKNOWN = 0;    // unknown action   未定义
final static int DNA_TRANSFER = 1;   // transfer blocks to another datanode 数据块复制
final static int DNA_INVALIDATE = 2; // invalidate blocks 数据块删除
final static int DNA_SHUTDOWN = 3;   // shutdown node 关闭datanode
final static int DNA_REGISTER = 4;   // re-register 重新注册datanode
final static int DNA_FINALIZE = 5;   // finalize previous upgrade 提交上一次升级
final static int DNA_RECOVERBLOCK = 6;  // request a block recovery 数据块恢复
final static int DNA_ACCESSKEYUPDATE = 7;  // update access key 安全相关
final static int DNA_BALANCERBANDWIDTHUPDATE = 8; // update balancer bandwidth  更新平衡器带宽
final static int DNA_CACHE = 9;      // cache blocks  缓存数据块
final static int DNA_UNCACHE = 10;   // uncache blocks  解缓存数据块

DatanodeProtocol中定义的指令类型数量和DatanodeCommand子类的数量并不一致,这是因为指令编号DNA_SHUTDOWN已经废弃不用了,Datanode接收到DNA_SHUTDOWN指令后会直接抛出UnsupportedOperationException异常。关闭Datanode是通过ClientDatanodeProtocol.shutdownDatanode()方法来触发的

DNA_TRANSFER、DNA_RECOVERBLOCK以及DNA_INVALIDATE都是通过BlockCommand子类来封装的,只不过参数不同。

DNA_TRANSFER指令用于触发Datanode的数据块复制操作,当HDFS系统中某个数据块的副本数小于配置的副本系数时,Namenode会通过DNA_TRANSFER指令通知某个拥有这个数据块的副本的Datanode将该数据块复制到其他Datanode上。

DNA_INVALIDATE用于通知Datanode删除Datanode上的指定数据块,这是因为Namenode发现了某个数据块的副本数已经超过了配置的副本系数,这时Namenode会通知某个Datanode删除这个Datanode上多余的数据块副本。

当客户端在写文件时发生异常退出,会造成数据流管道中不同Datanode上数据块状态的不一致,这时Namenode会从数据流管道中选出一个数据节点作为主恢复节点,协调数据流管道中的其他数据节点进行租约恢复操作,以同步这个数据块的状态。此时Namenode就会向这个Datanode下发DNA_RECOVERBLOCK指令,通知数据节点开始租约恢复操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值