一、概述

Namenode 启动后,会加载 fsimage 和 editlog 文件重建文件系统目录树,但是对于数据块与 Datanode 的映射关系却需要在 Datanode 上报后动态构建。Datanode 在启动时除了会与名字节点握手、注册以及上报数据块信息外,还会定时向 Namenode 发送心跳以及块汇报,并执行 Namenode 传回的指令。所以 Namenode 中会有很大一部分逻辑是与 Datanode 相关的,包括添加和删除 Datanode、与 Datanode 启动过程的交互、处理 Datanode 发送的心跳。

二、架构设计

正在持续施工中ing~~~

三、实现

3.1. 相关类

3.1.1. 数据节点标识

  1. DatanodeId

    DatanodeID 用于唯一标识一个 Datanode, Datanode 是通过 <ip,port> 以及 storageId 进行标识

  2. DatanodeInfo

    DatanodeInfo 类扩展自 DatanodeId,它携带了一些比较简单的 Datanode 信息

  3. DatanodeDescriptor

    数据节点描述符 DatanodeDescriptor 是 Namenode 中对 Datanode 的抽象,继承自 DatanodeInfo 类,这个类只用在 Namenode 侧,对于 Client 是不可见的。DatanodeDescriptor 继承自 DatanodeInfo 类。

3.1.2. DatanodeStorageInfo

DatanodeStoragelnfo 类描述了 Datanode 上的一个存储(storage),一个 Datanode 可以定义多个存储(在 dfs.datanode.data.dir 中配置多个 Datanode 的存储目录)保存数据块,这些存储还可以是异构的,例如可以是磁盘、内存、SSD 等。

在 HDFS 2.6 版本前,Namenode 内存中维护的是数据块与保存数据块副本的数据节点的对应关系,也就是 Block 与DatanodeDescriptor 的对应关系。在 HDFS 2.6 版本中,为了支持 Datanode 的异构存储特性,Namenode 内存中维护的变成了数据块与保存数据块副本的数据节点存储的对应关系,也就是 Block 与 DatanodeStorageInfo 的对应关系,同时块汇报的单位也由 Datanode 变为了 DatanodeStorageInfo。

3.1.3. DatanodeManager

DatanodeManager 类中记录了在 Namenode 上注册的 Datanode,以及这些 Datanode 在网络中的拓扑结构等信息。

DatanodeManager

3.2. 添加/撤销 Datanode

HDFS 的一个重要特征就是具有弹性,也就是当 HDFS 需要增加容量时,可以动态地向集群中添加新的 Datanode,同理,当 HDFS 需要减小规模时,可以动态地撤销已经存在的 Datanode。无论是添加还是撤销 Datanode 的操作,都不会影响 HDFS 服务。

HDFS 提供了 dfs.hosts 文件(由配置项 dfs.hosts 指定,又称 include 文件)以及 dfs.hosts.exclude 文件(由配置 dfs.hosts.exclude 指定,简称 exclude 文件)管理接入到 HDFS的 Datanode。include 文件指定了可以连接到 Namenode 的 Datanode 列表,exclude 文件指定
了不能连接到 Namenode 的 Datanode 列表,这两个文件都是文本文件,一行表示一个 Datanode。

一个未定义或空的 include 文件意味着所有 Datanode 都可以连接到 Namenode。

![](/Users/joker/Library/Application Support/typora-user-images/截屏2023-01-23 11.44.15.png)

HDFS 管理员将一个 Datanode 添加到集群中时,需要在 include 文件中添加一条该 Datanode 的记录,然后调用 dfsadmin -refreshNodes 命令刷新名字节点信息,最后启动 Datanode。同理,撤销节点则通过 exclude 文件,管理员将要撤销的节点信息添加到 exclude 文件中,也是调用 dfsadmin -refreshNodes 命令,Namenode 就会开始撤销节点操作。被撤销节点上的数据块会被复制到集群中的其他 Datanode 上,在这个过程中 Datanode 处于 “正在撤销状态”,数据复制完成后 Datanode 状态会转变为 “已撤销”,这时就可以关闭 Datanode 了。

3.2.1. refreshNodes()

执行 dfsadmin -refreshNodes 命令会调用 RPC 方法 ClientProtocol.refreshNodes() 通知 Namenode 更新 include 文件和 exclude 文件,这个操作最终会由 DatanodeManager.refreshNodes() 方法响应。

refreshNodes
  1. refreshHostsReader()

    refreshNodes() 方法会首先调用 refreshHostsReader() 方法将 include 文件与 exclude 文件加载到 hostFileManager 对象中,之后调用 refreshDatanodes() 刷新所有的数据节点。

  2. refreshDatanodes()

    refreshDatanodes() 方法遍历 DatanodeManager.datanodeMap 字段中保存的所有 DatanodeDescriptor 对象,对于不可以连接到 Namenode 的 Datanode,设置对应的 DatanodeDescriptor.isAllowed 字段为 false,表明当前 Datanode 不可以接入 HDFS 集群。对于 exclude 文件中的节点,需要进行撤销操作,则调用 startDecommission() 开始撤销操作;不在 exclude 文件中的节点,则调用 stopDecommission() 停止撤销操作。

    DatanodeInfo.adminState 字段用于标识当前 Datanode 的状态。如果 adminState == null,则表明当前 Datanode 处于正常状态:如果为
    AdminStates.DECOMMISSION_INPROGRESS ,则表明 Datanode 处于正在撤销状态;如果为 AdminStates.DECOMMISSIONED ,则表明 Datanode 已经撤销了。

    startDecommission() 方法会首先调用 heartbeatManager.startDecommission() 将当前 Datanode 对应的 DatanodeDescriptor.adminState 设置为 AdminStates.DECOMMISSION_INPROGRESS 状态。之后调用 checkDecommissionState() 检查撤销操作是否完成,如果完成则将 DatanodeDescriptor.adminState 设置为 AdminStates.DECOMMISSIONED 。checkDecommissionState() 方法会调用 BlockManager.isReplicationInProgress() 判断当前节点上保存的所有数据块是否满足副本系数,如果不满足则将该数据块加入 neededReplications 中进行复制操作,撤销节点上数据块的复制操作都是由这个方法触发的。

  3. countSoftwareVersions()

3.2.2. DataNode 启动