DataNode的分析

相对NN,DN主要就是对数据块的副本进行操作,如增删改等操作,管理DN中的这些副本,另外提供对副本的接口给client,NN,其他的DN。

startDataNode() 方法:

首先从配置文件中读取与DN 相关的配置参数。

NN 进行握手。

根据参数配置好的数据块存放的文件目录,为每个目录建立起 DataStorage ,然后调用该类的 recoverTransitionRead 方法去读取存储元信息,锁住目录,然后转变文件状态。做一些格式化,恢复,回滚,升级操作。

然后调用 FSDataset 构造函数,读取上一步的文件目录下的副本文件,将副本状态信息保存到内存列队中,同时创建线程池用于执行对副本的异步删除操作。

实始化数据块扫描类 DataBlockScanner

最后,启动一系列服务端口,如接收数据的端口,web server 访问端口等。

run() 方法:

首先启动 DN 的数据接收服务守护线程 DataXceiverServer

然后循环做以下操作:

判断是否需要更新,如参数发生变化了,则需要重新初始化 DN

发送心跳,发送最近接收的 block ,报告 DN 当前的 block 列表给 NN

接收 NN 发送给 DN 的相关命令,并且执行命令。

报告 DN 当前的所有 block 列表的时间间隔相对要长很多,默认是 1 个小时报告一次。

启动 DataBlockScanner 线程。

 

NN DN 发送的命令

DataNode 类中的 processCommand() 方法完成。

副本复制:

NN 告诉DNDN 中存在的block 传送给其他DN, 主要是由于NN 检测到该数据块的副本数少于3 ,因此需要将副本复制给其他DN ,于是通知副本所在的DN ,将副本数据推送给其他的DN 。其中NN 将数据块的信息和目标的DN 信息存放在类 BlockCommand DN 收到这些信息后,解析出数据块和目标DN 信息,分别将相应的数据块传送给相应的目标DN

  private void transferBlocks( Block blocks[],

                               DatanodeInfo xferTargets[][]

                               ) {

    for ( int i = 0; i < blocks. length ; i++) {

      try {

        transferBlock(blocks[i], xferTargets[i]);

      } catch (IOException ie) {

        LOG .warn( "Failed to transfer block " + blocks[i], ie);

      }

    }

  }

然后判断该副本的在DN 磁盘上的大小小于NN 中存放的大小时,则该副本是不可用的,然后调用 namenode .reportBadBlocks ()告诉NN ,这副本是坏的。

否则启动线程去进行副本传送工作。

new Daemon( new DataTransfer (xferTargets, block, this )).start();

DataTransfer 线程里面将会调用BlockSender. sendBlock() 将副本数据分成多个packs 传送给相应的目标DN

标识无效的数据块:

同样从NN 中拿到无效的数据块,因为客户已经将数据块删除了。

一方面是告诉DataBlockScanner 不再去对该数据块进行扫描检查工作。

另一方面,告诉FSDataset ,从内存的副本集合中删除掉该副本,同时调用FSDatasetAsyncDiskServicedeleteAsync ()异步的删除该副本在本地磁盘上的数据文件和元信息文件。

if ( blockScanner != null ) {

          blockScanner . deleteBlocks (toDelete);

        }

        data .invalidate(toDelete);

数据块恢复:

将为每个要恢复的数据块建立一个线程来完成恢复工作。

public Daemon recoverBlocks( final Collection<RecoveringBlock> blocks) {

    Daemon d = new Daemon( threadGroup , new Runnable() {

      /** Recover a list of blocks. It is run by the primary datanode. */

      public void run() {

        for (RecoveringBlock b : blocks) {

          try {

            logRecoverBlock ( "NameNode" , b.getBlock(), b.getLocations());

            recoverBlock(b);

          } catch (IOException e) {

            LOG .warn( "recoverBlocks FAILED: " + b, e);

          }

        }

      }

    });

    d.start();

    return d;

  }

然后分别去该数据块的其他DN 上查询到在这些DN 上副本记录。

然后进行数据块恢复工作。

如果所有的DN 上该数据块都没有保存数据,则告诉调用

namenode .commitBlockSynchronization(block, recoveryId, 0,

          true , true , DatanodeID. EMPTY_ARRAY );

告诉NN ,这是一个空的数据块。

不然的话,计算该数据块最佳可用的副本状态。即取所有的DN 中收到数据值最小作为可用的数据。

最后,在将该数据块的状态告诉其他的DNNN

Block reply = r. datanode .updateReplicaUnderRecovery(

            r. rInfo , recoveryId, newBlock.getNumBytes());

namenode .commitBlockSynchronization(block,

        newBlock.getGenerationStamp(), newBlock.getNumBytes(), true , false ,

        nlist);

 

关闭DN

DN 中的守护线程都停止掉。如:DataXceiverServerDataBlockScannerDataNode 的心跳线程,以及与NNRPC 通信线程等。

重新注册:

调用RPC ,告之NNDN 当前的通信信息。

dnRegistration = namenode .registerDatanode( dnRegistration );

另外,设置BR 的周期。

scheduleBlockReport( initialBlockReportDelay );

完成升级:

由于升级完成,因为在升级之前,为了保证数据不会丢失,将当前的文件目录都从.cuttent 移到了. Previous ,升级成功后,将.pervious 目录移到.tmp 目录下,然后删除.tmp 临时目录下的所有数据。

client, 其他的 DN 与该 DN 的数据传输

DataXceiver 类中的run() 方法完成。

读数据块:

建立起向客户端的输出流,然后调用BlockSender 类将数据块副本的数据从磁盘读取后发送给客户端。

对应于客户端来DN 中读取相应的数据块。

写数据块:

收到一个数据块的数据,将其写到磁盘上,同时要向下一个DN 传送该数据块的数据。

对应于客户端或者上游DN 向本地DN 写数据。

替换数据块

向相应的DN 发送复制操作,然后接收该DN 发送来的数据块数据,并且将数据保存到磁盘中。

复制数据块

从磁盘中读取到数据块的数据,然后将数据发送给发送复制操作给自已的目标DN

获得数据块的校验数据:

从元数据文件里获得校验数据,然后发送给客户端。

DataStorage

存储信息的管理类。

protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>();

变量保存着文件目录列表。

 

recoverTransitionRead ()

DN 构造时会调用该函数,在读文件目录前做回滚和保存现场,用于防止启动失败,数据丢失。

void recoverTransitionRead(NamespaceInfo nsInfo,

                             Collection<File> dataDirs,

                             StartupOption startOpt

                             )

为每个数据目录计算状态,确定一致性。根据当前的不同状态进行格式化或者恢复操作。

参数dfs.datanode.data.dir 中由管理员配置副本存放的文件目录,可以有多个目录,用, 分开。

switch (curState) {

        case NORMAL :

          break ;

        case NON_EXISTENT :

          // ignore this storage

          LOG .info( "Storage directory " + dataDir + " does not exist." );

          it.remove();

          continue ;

         case NOT_FORMATTED : // format

          LOG .info( "Storage directory " + dataDir + " is not formatted." );

          LOG .info( "Formatting ..." );

          format(sd, nsInfo);

          break ;

        default // recovery part is common

          sd.doRecover(curState);

        }

然后再进行事务处理。

   for ( int idx = 0; idx < getNumStorageDirs(); idx++) {

      doTransition(getStorageDir(idx), nsInfo, startOpt);

      assert this .getLayoutVersion() == nsInfo.getLayoutVersion() :

        "Data-node and name-node layout versions must be the same." ;

      assert this .getCTime() == nsInfo.getCTime() :

        "Data-node and name-node CTimes must be the same." ;

    }

  回滚操作:current 命名为tmpprevious to current ,再删除tmp 目录。

升级操作: 清空 detach 目录,删除previous 目录,将current 命名为tmp 目录,

finalized RBW 状态的数据在current 目录里创建硬链接指向tmp 目录中的数据块文件。

最后在将tmp 目录命名为previous 目录。

再将本次启动的事务信息写入到文件中,如版本号之类的。

this .writeAll();

 

FSDataset

副本集合的管理类。

副本列表(数据块列表)。

ReplicasMap volumeMap = new ReplicasMap();

 

FSDataset ()

DN 构造函数时调用该类构造函数,用于实始化DN 中的内部结构。

调用FSVolumeSet 类中的方法getVolumeMap (),将相应的副本信息读入到内存中,即副本队列中。扫描多个数据目录,以及目录下的各个子目录,子文件。

  void getVolumeMap(ReplicasMap volumeMap) throws IOException {

      // add finalized replicas

      dataDir .getVolumeMap(volumeMap, this );

      // add rbw replicas

      addToReplicasMap(volumeMap, rbwDir , false );

    }

最后创建一个磁盘异步服务类 FSDatasetAsyncDiskService

    File[] roots = new File[storage.getNumStorageDirs()];

    for ( int idx = 0; idx < storage.getNumStorageDirs(); idx++) {

      roots[idx] = storage.getStorageDir(idx).getCurrentDir();

    }

    asyncDiskService = new FSDatasetAsyncDiskService(roots);

 

FSDatasetAsyncDiskService

为每个数据块目录创建一个线程池,并用作为一个线程组,池的最小值为1 ,最大值为4 ,当前版本,只有delete 操作会将任务经过线程池调度来进行异步处理,读写操作都是调文件操作同步去执行的。

  FSDatasetAsyncDiskService(File[] volumes) {

    threadFactory = new ThreadFactory() {

      public Thread newThread(Runnable r) {

        return new Thread( threadGroup , r);

      }

    };

    // Create one ThreadPool per volume

    for ( int v = 0 ; v < volumes. length ; v++) {

      ThreadPoolExecutor executor = new ThreadPoolExecutor(

          CORE_THREADS_PER_VOLUME , MAXIMUM_THREADS_PER_VOLUME ,

          THREADS_KEEP_ALIVE_SECONDS , TimeUnit. SECONDS ,

          new LinkedBlockingQueue<Runnable>(), threadFactory );

 

      // This can reduce the number of running threads

       executor.allowCoreThreadTimeOut( true );

      executors .put(volumes[v], executor);

    }

 

最后来一张副本的状态变化图



 

想要知道更多的细节,看源代码是最清楚的,同时自已可以调试运行代码。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值