DataNode的分析

来源:互联网 发布:网络教育几月交学费 编辑:程序博客网 时间:2024/05/19 17:07

相对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线程。

 

NNDN发送的命令

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

副本复制:

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

  private void transferBlocks(Block blocks[],

                               DatanodeInfoxferTargets[][]

                               ) {

    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 = newArrayList<StorageDirectory>();

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

 

recoverTransitionRead()

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

void recoverTransitionRead(NamespaceInfonsInfo,

                            Collection<File> dataDirs,

                             StartupOptionstartOpt

                             )

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

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

switch(curState) {

        case NORMAL:

          break;

        case NON_EXISTENT:

          // ignore thisstorage

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

         it.remove();

          continue;

        case NOT_FORMATTED: // format

          LOG.info("Storagedirectory " + 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 andname-node layout versions must be the same.";

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

        "Data-node andname-node CTimes must be the same.";

    }

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

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

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

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

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

this.writeAll();

 

FSDataset

副本集合的管理类。

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

ReplicasMap volumeMap = new ReplicasMap();

 

FSDataset()

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

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

  voidgetVolumeMap(ReplicasMap volumeMap) throws IOException {

      // add finalizedreplicas

      dataDir.getVolumeMap(volumeMap,this);

      // add rbw replicas

     addToReplicasMap(volumeMap, rbwDir, false);

    }

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

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

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

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

    }

    asyncDiskService = newFSDatasetAsyncDiskService(roots);

 

FSDatasetAsyncDiskService

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

 FSDatasetAsyncDiskService(File[] volumes) {

    threadFactory = new ThreadFactory() {

      public ThreadnewThread(Runnable r) {

        return new Thread(threadGroup, r);

      }

    };

    // Create oneThreadPool 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,

          newLinkedBlockingQueue<Runnable>(), threadFactory);

 

      // This can reducethe number of running threads

      executor.allowCoreThreadTimeOut(true);

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

    }

 

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



 

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


原创粉丝点击