HDFS datanode源码分析

datanode的介绍

一个典型的HDFS系统包括一个NameNode和多个DataNode。DataNode是hdfs文件系统中真正存储数据的节点。

每个DataNode周期性和唯一的NameNode通信,还时不时和hdfs客户端代码以及其他datanode通信。

 

datanode维护一个重要的表:

  块=>字节流

这些存储在本地磁盘,DataNode在启动时,还有启动后周期性报告给NameNode,这个表的内容。

DataNodes周期性请求NameNode询问命令操作,NameNode不能直接连接DataNode,NameNode在DataNode调用时,简单返回值。

DataNodes还维护一个开放的socket服务器,让客户端代码和其他DataNode通过它可以读写数据,这个服务器的host/port会汇报给NameNode。

 

datanode启动流程

在命令行启动datanode的方法是:bin/hadoop datanode

查看bin/hadoop脚本,可以看到最后执行的java类是:org.apache.hadoop.hdfs.server.datanode.DataNode

DataNode的骨架成员如下:

复制代码
public class DataNode extends Configured implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants,Runnable, DataNodeMXBean {
  public DatanodeProtocol namenode = null;//与NameNode通信的ipc客户端类
  public FSDatasetInterface data = null;//管理一系列的数据块,每个块在本地磁盘上都有唯一的名字和扩展名。所有和数据块相关的操作,都在FSDataset相关的类中进行处理。
  public DatanodeRegistration dnRegistration = null;//DataNode向NameNode的注册信息,包含名字(datanode机器名:dfs.datanode.address端口),info的http端口,ipc的端口等

  volatile boolean shouldRun = true;//DataNode循环运行标志,为true就一直运行
  private LinkedList<Block> receivedBlockList = new LinkedList<Block>();//已经接收的数据块,定期通知namenode接收完毕时,会移除
  private final Map<Block, Block> ongoingRecovery = new HashMap<Block, Block>();//存放正在从本地块恢复到其他DataNode的数据块,恢复完毕后移除,在其他DataNode的数据块副本损坏或丢失时会使用
  private LinkedList<String> delHints = new LinkedList<String>(); //需要删除的块,一般是被替换时才会被删除,也是在定期通知namenode后,会移除

  Daemon dataXceiverServer = null;//用于读写数据的服务器,接收客户端和其他DataNode的请求,它不用于内部hadoop ipc机制,端口是dfs.datanode.address
  public Server ipcServer; //内部datanode调用的ipc服务器,用于客户端,端口是dfs.datanode.ipc.address
  
  long blockReportInterval;//数据块报告周期,默认是60*60秒,即一个小时
  long lastBlockReport = 0;//记录最近的数据块报告时间,与blockReportInterval联合使用


  long lastHeartbeat = 0;//记录最近和namenode的心跳时间
  long heartBeatInterval;//和namenode的心跳周期,默认是3s
  private DataStorage storage = null;//DataStorage提供了format方法,用于创建DataNode上的Storage,对DataNode的升级/回滚/提交过程,就是对DataStorage的doUpgrade/doRollback/doFinalize分析得到的。同时,利用StorageDirectory,DataStorage管理存储系统的状态。
  private HttpServer infoServer = null;//查看DataNode状态信息的http服务器,端口是dfs.datanode.http.address
  
  public DataBlockScanner blockScanner = null;//检测它所管理的所有Block数据块的一致性,因此,对已DataNode节点上的每一个Block,它都会每隔scanPeriod ms(默认三个星期)利用Block对应的校验和文件来检测该Block一次,看看这个Block的数据是否已经损坏。
  public Daemon blockScannerThread = null;
  
}
复制代码

 

DataNode的初始化和启动:

复制代码
public class DataNode extends Configured implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants,Runnable, DataNodeMXBean {
  //main方法,DataNode的入口点
  public static void main(String args[]) {
    secureMain(args, null);
  }
  
  //主线程阻塞,让DataNode的任务循环执行
  public static void secureMain(String [] args, SecureResources resources) {
    try {
      ...
      DataNode datanode = createDataNode(args, null, resources);
      if (datanode != null)
        datanode.join();
    }
    ...
  }
  
  public static DataNode createDataNode(String args[],Configuration conf, SecureResources resources) throws IOException {
    DataNode dn = instantiateDataNode(args, conf, resources);
    runDatanodeDaemon(dn);//DataNode类作为一个Thread运行
    return dn;
  }
  
  public static DataNode instantiateDataNode(String args[],Configuration conf, SecureResources resources) throws IOException {
    ...
    String[] dataDirs = conf.getStrings(DATA_DIR_KEY);//获取DataNode管理的本地目录集合
    return makeInstance(dataDirs, conf, resources);
  }
  
  //检查本地目录集合的合法性
  public static DataNode makeInstance(String[] dataDirs, Configuration conf, SecureResources resources) throws IOException {
    ...
    ArrayList<File> dirs = new ArrayList<File>();
    FsPermission dataDirPermission = new FsPermission(conf.get(DATA_DIR_PERMISSION_KEY, DEFAULT_DATA_DIR_PERMISSION));
    for (String dir : dataDirs) {
      ...
        DiskChecker.checkDir(localFS, new Path(dir), dataDirPermission);
        dirs.add(new File(dir));
      ...
    }
    if (dirs.size() > 0) 
      return new DataNode(conf, dirs, resources);
    return null;
  }
  
  //实例化DataNode
  DataNode(final Configuration conf,final AbstractList<File> dataDirs, SecureResources resources) throws IOException {
    super(conf);
    ...
    try {
      startDataNode(conf, dataDirs, resources);
    } catch (IOException ie) {
      shutdown();
      throw ie;
    }   
  }
  
   void startDataNode(Configuration conf, AbstractList<File> dataDirs, SecureResources resources) throws IOException {
    
    InetSocketAddress nameNodeAddr = NameNode.getServiceAddress(conf, true);

    InetSocketAddress socAddr = DataNode.getStreamingAddr(conf);//获取DataNode的数据块流的读写的端口
    int tmpPort = socAddr.getPort();
    storage = new DataStorage();//管理数据目录的类,完成格式化,升级,回滚等功能
    // construct registration
    this.dnRegistration = new DatanodeRegistration(machineName + ":" + tmpPort);

    //与namenode通信的客户端类
    this.namenode = (DatanodeProtocol) RPC.waitForProxy(DatanodeProtocol.class,DatanodeProtocol.versionID,nameNodeAddr, conf);
    //从NameNode获取版本和id信息
    NamespaceInfo nsInfo = handshake();

    if (simulatedFSDataset) {
        ...
    } else { // real storage
      // read storage info, lock data dirs and transition fs state if necessary
      storage.recoverTransitionRead(nsInfo, dataDirs, startOpt);
      // adjust
      this.dnRegistration.setStorageInfo(storage);
      // initialize data node internal structure
      this.data = new FSDataset(storage, conf);//一切数据块读写的实际操作类
    }
      
    ...
    this.dataXceiverServer = new Daemon(threadGroup, new DataXceiverServer(ss, conf, this));//初始化数据块的流读写服务器
    ...
    //初始化数据块报告周期,默认是一个小时
    this.blockReportInterval = conf.getLong("dfs.blockreport.intervalMsec", BLOCKREPORT_INTERVAL);
    ...
    //初始化与namenode心跳周期,默认是3秒
    this.heartBeatInterval = conf.getLong("dfs.heartbeat.interval", HEARTBEAT_INTERVAL) * 1000L;
    ...
    if ( reason == null ) {
      blockScanner = new DataBlockScanner(this, (FSDataset)data, conf);//初始化数据块一致性检测类
    } 
    ...

    //DataNode的状态信息查询的http服务器地址
    InetSocketAddress infoSocAddr = DataNode.getInfoAddr(conf);
    ...
    //初始化DataNode的状态信息查询的http服务器
    this.infoServer = (secureResources == null) 
       ? new HttpServer("datanode", infoHost, tmpInfoPort, tmpInfoPort == 0, 
           conf, SecurityUtil.getAdminAcls(conf, DFSConfigKeys.DFS_ADMIN))
       : new HttpServer("datanode", infoHost, tmpInfoPort, tmpInfoPort == 0,
           conf, SecurityUtil.getAdminAcls(conf, DFSConfigKeys.DFS_ADMIN),
           secureResources.getListener());
    ...
    //添加infoServer一些Servlet的映射url和类
    ...
    this.infoServer.start();
    ...
    //初始化内部hadoop ipc服务器
    InetSocketAddress ipcAddr = NetUtils.createSocketAddr(
        conf.get("dfs.datanode.ipc.address"));
    ipcServer = RPC.getServer(this, ipcAddr.getHostName(), ipcAddr.getPort(), 
        conf.getInt("dfs.datanode.handler.count", 3), false, conf,
        blockTokenSecretManager);
    dnRegistration.setIpcPort(ipcServer.getListenerAddress().getPort());
    ...
  }
复制代码

 

DataNode的服务:

复制代码
//运行DataNode的后台线程
  public static void runDatanodeDaemon(DataNode dn) throws IOException {
    if (dn != null) {
      //register datanode
      dn.register();
      dn.dataNodeThread = new Thread(dn, dnThreadName);
      dn.dataNodeThread.setDaemon(true); 
      dn.dataNodeThread.start();
    }
  }
  //启动数据块的流读写服务器,内部hadoop ipc服务器
  public void run() {
    ...
    dataXceiverServer.start();
    ipcServer.start();
        
    while (shouldRun) {
      try {
        startDistributedUpgradeIfNeeded();//检测是否需要升级hadoop文件系统
        offerService();//DataNode提供服务,定时发送心跳给NameNode,响应NameNode返回的命令并执行
      } 
      ...
    }
  }
  
  //DataNode提供服务,定时发送心跳给NameNode,响应NameNode返回的命令并执行,通知namenode接收完毕的数据块和删除的数据块,定时上报数据块
  public void offerService() throws Exception {
    ...
    while (shouldRun) {
      try {
        long startTime = now();
        ...
        if (startTime - lastHeartbeat > heartBeatInterval) {
          lastHeartbeat = startTime;
          //定期发送心跳给NameNode
          DatanodeCommand[] cmds = namenode.sendHeartbeat(dnRegistration,
                                                       data.getCapacity(),
                                                       data.getDfsUsed(),
                                                       data.getRemaining(),
                                                       xmitsInProgress.get(),
                                                       getXceiverCount());
          ...
          //响应namenode返回的命令做处理
          if (!processCommand(cmds))
            continue;
        }
        
        synchronized(receivedBlockList) {
          synchronized(delHints) {
              blockArray = receivedBlockList.toArray(new Block[numBlocks]);
              delHintArray = delHints.toArray(new String[numBlocks]);
            }
          }
        }
        if (blockArray != null) {
          //通知NameNode已经接收完毕的块,以及删除的块
          namenode.blockReceived(dnRegistration, blockArray, delHintArray);
          synchronized (receivedBlockList) {
            synchronized (delHints) {
              for(int i=0; i<blockArray.length; i++) {
                receivedBlockList.remove(blockArray[i]);//清空保存接收完毕的块
                delHints.remove(delHintArray[i]);//清空保存删除完毕的块
              }
            }
          }
        }
        
       if (startTime - lastBlockReport > blockReportInterval) {
          if (data.isAsyncBlockReportReady()) {
            // Create block report
            ...
            Block[] bReport = data.retrieveAsyncBlockReport();
            ...
            //向NameNode上报数据块信息
            DatanodeCommand cmd = namenode.blockReport(dnRegistration,
                    BlockListAsLongs.convertToArrayLongs(bReport));
            ...
            processCommand(cmd);
          } else {
            //请求异步准备好数据块上报信息
            data.requestAsyncBlockReport();
            ...
            }
          }
        }
        
    } // while (shouldRun)
  } // offerService
}
复制代码

以上就是DataNode的启动流程和服务流程,都以作适当删减,留下主干,加上注释。

 

DataNode的相关重要类

FSDataset:所有和数据块相关的操作,都在FSDataset相关的类。详细分析参考 http://caibinbupt.iteye.com/blog/284365

DataXceiverServer:处理数据块的流读写的的服务器,处理逻辑由DataXceiver完成。详细分析参考 http://caibinbupt.iteye.com/blog/284979

DataXceiver:处理数据块的流读写的线程。详细分析参考 http://caibinbupt.iteye.com/blog/284979

                  还有处理非读写的非主流的流程。详细分析参考 http://caibinbupt.iteye.com/blog/286533

BlockReceiver:完成数据块的流写操作。详细分析参考 http://caibinbupt.iteye.com/blog/286259

BlockSender:完成数据块的流读操作。

DataBlockScanner:用于定时对数据块文件进行校验。详细分析参考http://caibinbupt.iteye.com/blog/286650

 

总结

上面讲了DataNode相关的核心类的成员和初始化流程,并做了代码的删减,留下主干,加上注释,让初学者可以概览DataNode的源码,快速入门。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值