• hadoop源码_hdfs启动流程_1_NameNode


    执行start-dfs.sh脚本后,集群是如何启动的?
    本文阅读并注释了start-dfs脚本,以及namenode和datanode的启动主要流程流程源码。

    阅读源码前准备

    源码获取

    1. 拉取Apache Hadoop官方源码

      https://github.com/apache/hadoop

    2. 用idea打开...

    3. 切换到想看的版本...

      这里用的最新版本3.3.1

    阅读目标

    ​ 本篇的阅读目标是搞明白hadoop中的start-dfs.sh启动脚本执行后都做了什么,hadoop中的NameNode,DataNode启动过程等大致流程,不会细追细节

    start-dfs.sh 干了什么

    hdfs集群的启动命令为:start-dfs.sh, 脚本的位置在下图中:

    ![image-

    ​ 脚本中大致分位两块内容,第一部分是调用hdfs-config.sh脚本配置hdfs以及hadoop的参数以及环境等,第二部分是启动datanode、namenode以及secondary namenode等等。我们的重点是看第二部分的启动流程。

    hdfs-config 简述

    ​ start-dfs.sh中启动hdfs-config.sh的代码如下:

    # let's locate libexec...
    if [[ -n "${HADOOP_HOME}" ]]; then
      HADOOP_DEFAULT_LIBEXEC_DIR="${HADOOP_HOME}/libexec"
    else
      HADOOP_DEFAULT_LIBEXEC_DIR="${bin}/../libexec"
    fi
    
    HADOOP_LIBEXEC_DIR="${HADOOP_LIBEXEC_DIR:-$HADOOP_DEFAULT_LIBEXEC_DIR}"
    # shellcheck disable=SC2034
    HADOOP_NEW_CONFIG=true
    if [[ -f "${HADOOP_LIBEXEC_DIR}/hdfs-config.sh" ]]; then
      . "${HADOOP_LIBEXEC_DIR}/hdfs-config.sh"
    else
      echo "ERROR: Cannot execute ${HADOOP_LIBEXEC_DIR}/hdfs-config.sh." 2>&1
      exit 1
    fi
    

    ​ 在hdfs-config.sh脚本中会尝试启动hdfs-evn.sh脚本(如果存在)
    ​ 之后会检查以及设置HDFS的各种参数,例如:

      # turn on the defaults
      export HDFS_AUDIT_LOGGER=${HDFS_AUDIT_LOGGER:-INFO,NullAppender}
      export HDFS_NAMENODE_OPTS=${HDFS_NAMENODE_OPTS:-"-Dhadoop.security.logger=INFO,RFAS"}
      export HDFS_SECONDARYNAMENODE_OPTS=${HDFS_SECONDARYNAMENODE_OPTS:-"-Dhadoop.security.logger=INFO,RFAS"}
      export HDFS_DATANODE_OPTS=${HDFS_DATANODE_OPTS:-"-Dhadoop.security.logger=ERROR,RFAS"}
      export HDFS_PORTMAP_OPTS=${HDFS_PORTMAP_OPTS:-"-Xmx512m"}
    
      # depending upon what is being used to start Java, these may need to be
      # set empty. (thus no colon)
      export HDFS_DATANODE_SECURE_EXTRA_OPTS=${HDFS_DATANODE_SECURE_EXTRA_OPTS-"-jvm server"}
      export HDFS_NFS3_SECURE_EXTRA_OPTS=${HDFS_NFS3_SECURE_EXTRA_OPTS-"-jvm server"}
    

    ​ 再之后会启动hadoop-config.sh脚本:

    # shellcheck source=./hadoop-common-project/hadoop-common/src/main/bin/hadoop-config.sh
    
    if [[ -n "${HADOOP_COMMON_HOME}" ]] &&
       [[ -e "${HADOOP_COMMON_HOME}/libexec/hadoop-config.sh" ]]; then
      . "${HADOOP_COMMON_HOME}/libexec/hadoop-config.sh"
    elif [[ -e "${HADOOP_LIBEXEC_DIR}/hadoop-config.sh" ]]; then
      . "${HADOOP_LIBEXEC_DIR}/hadoop-config.sh"
    elif [ -e "${HADOOP_HOME}/libexec/hadoop-config.sh" ]; then
      . "${HADOOP_HOME}/libexec/hadoop-config.sh"
    else
      echo "ERROR: Hadoop common not found." 2>&1
      exit 1
    fi
    

    hadoop-config.sh是最基本的、公用的环境变量配置脚本,会再调用etc/hadoop/hadoop-env.sh脚本。主要是配置java启动项相关参数,比如java执行环境的classpath等。

    hdfs-config.sh一系列脚本的整体功能就是保证启动hdfs集群前对hdfs和hadoop的各种环境变量进行配置。

    start-dfs.sh后续就是逐步启动各个节点(namenodes,datanodes,secondary namenodes,quorumjournal nodes,quorumjournal nodes),如果是ha集群还会启动ZK Failover controllers

    NameNode 启动流程

    脚本代码

    start-dfs.sh中启动namenode的代码:

    #---------------------------------------------------------
    # namenodes
    # 找到配置中(如果没有配置取当前节点为)的namenode节点
    NAMENODES=$("${HADOOP_HDFS_HOME}/bin/hdfs" getconf -namenodes 2>/dev/null)
    
    if [[ -z "${NAMENODES}" ]]; then
      NAMENODES=$(hostname)
    fi
    # 执行启动命令
    echo "Starting namenodes on [${NAMENODES}]"
    hadoop_uservar_su hdfs namenode "${HADOOP_HDFS_HOME}/bin/hdfs" 
        --workers 
        --config "${HADOOP_CONF_DIR}" 
        --hostnames "${NAMENODES}" 
        --daemon start 
        namenode ${nameStartOpt}
    
    HADOOP_JUMBO_RETCOUNTER=$?
    

    hadoop-hdfs > src > mian > bin > hdfs中查看namenode命令:

    # 命令描述:用于启动namenode
      hadoop_add_subcommand "namenode" daemon "run the DFS namenode"
      
    # 命令处理程序
        namenode)
          HADOOP_SUBCMD_SUPPORTDAEMONIZATION="true"
          HADOOP_CLASSNAME='org.apache.hadoop.hdfs.server.namenode.NameNode'
          hadoop_add_param HADOOP_OPTS hdfs.audit.logger "-Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER}"
        ;;
        
    # 执行函数命令
    # everything is in globals at this point, so call the generic handler
    hadoop_generic_java_subcmd_handler
    

    这里就定位到了具体的处理类org.apache.hadoop.hdfs.server.namenode.NameNode。继续跟进到hadoop_generic_java_subcmd_handler函数定义的地方-脚本hdfs

    ## @description Handle subcommands from main program entries
    ## @audience private
    ## @stability evolving
    ## @replaceable yes
    function hadoop_generic_java_subcmd_handler
    {
    # ...... 省略
      # do the hard work of launching a daemon or just executing our interactive
      # java class
      if [[ "${HADOOP_SUBCMD_SUPPORTDAEMONIZATION}" = true ]]; then
        if [[ "${HADOOP_SUBCMD_SECURESERVICE}" = true ]]; then
          hadoop_secure_daemon_handler 
            "${HADOOP_DAEMON_MODE}" 
            "${HADOOP_SUBCMD}" 
            "${HADOOP_SECURE_CLASSNAME}" 
            "${daemon_pidfile}" 
            "${daemon_outfile}" 
            "${priv_pidfile}" 
            "${priv_outfile}" 
            "${priv_errfile}" 
            "${HADOOP_SUBCMD_ARGS[@]}"
        else
          hadoop_daemon_handler 
            "${HADOOP_DAEMON_MODE}" 
            "${HADOOP_SUBCMD}" 
            "${HADOOP_CLASSNAME}" 
            "${daemon_pidfile}" 
            "${daemon_outfile}" 
            "${HADOOP_SUBCMD_ARGS[@]}"
        fi
        exit $?
      else
        hadoop_java_exec "${HADOOP_SUBCMD}" "${HADOOP_CLASSNAME}" "${HADOOP_SUBCMD_ARGS[@]}"
      fi
    }
    
    function hadoop_java_exec
    {
      # run a java command.  this is used for
      # non-daemons
    
      local command=$1
      local class=$2
      shift 2
    
      hadoop_debug "Final CLASSPATH: ${CLASSPATH}"
      hadoop_debug "Final HADOOP_OPTS: ${HADOOP_OPTS}"
      hadoop_debug "Final JAVA_HOME: ${JAVA_HOME}"
      hadoop_debug "java: ${JAVA}"
      hadoop_debug "Class name: ${class}"
      hadoop_debug "Command line options: $*"
    
      export CLASSPATH
      #shellcheck disable=SC2086
      exec "${JAVA}" "-Dproc_${command}" ${HADOOP_OPTS} "${class}" "$@"
    }
    

    可以看到最终还是利用java命令来执行该类。

    NameNode 源码

    ​ 在源码中定位到org.apache.hadoop.hdfs.server.namenode.NameNode类。按照类的加载顺序来看NameNode启动流程:

    静态代码块

      static{
        HdfsConfiguration.init();
      }
    // 继续跟进代码,进入HdfsConfiguration类中:
    
    
    @InterfaceAudience.Private
    public class HdfsConfiguration extends Configuration {
      static {
        addDeprecatedKeys();
    
        // 加载默认的配置文件
        Configuration.addDefaultResource("hdfs-default.xml");
        Configuration.addDefaultResource("hdfs-rbf-default.xml");
        Configuration.addDefaultResource("hdfs-site.xml");
        Configuration.addDefaultResource("hdfs-rbf-site.xml");
      }
    
      /**
       * 这个方法在这里,这样当调用HdfsConfiguration时,如果它之前还没有被加载,它将被类加载。
       * 在加载类时,将执行上面的静态初始化块来添加已弃用的键并添加默认资源。
       * 这个方法被多次调用是安全的,因为静态初始化器块只会被调用一次。
       * 这取代了以前其他类直接调用Configuration.addDefaultResource("hdfs-default.xml")而不首先加载该类的危险做法,从而跳过了弃用键。
       */
      public static void init() {
      }
    

    mian方法

      public static void main(String argv[]) throws Exception {
        //分析传入的参数,是否是帮助参数
        if (DFSUtil.parseHelpArgument(argv, NameNode.USAGE, System.out, true)) {
          System.exit(0);
        }
    
        try {
          //打印一些启动日志信息
          StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
          //创建namenode
          NameNode namenode = createNameNode(argv, null);
          if (namenode != null) {
            //加入集群,HA,联邦集群都是有多个NameNode
            namenode.join();
          }
        } catch (Throwable e) {
          LOG.error("Failed to start namenode.", e);
          terminate(1, e);
        }
      }
    

    需要关注的是NameNode namenode = createNameNode(argv, null);

      public static NameNode createNameNode(String argv[], Configuration conf)
          throws IOException {
        // 日志信息
        LOG.info("createNameNode " + Arrays.asList(argv));
        if (conf == null)
          // 准备配置文件对象
          conf = new HdfsConfiguration();
        // 将一些通用参数解析到配置中。
        GenericOptionsParser hParser = new GenericOptionsParser(conf, argv);
        argv = hParser.getRemainingArgs();
        // 解析其余的NameNode特定参数,放到配置中。
        StartupOption startOpt = parseArguments(argv);
        if (startOpt == null) {
          printUsage(System.err);
          return null;
        }
        setStartupOption(conf, startOpt);
    
        boolean aborted = false;
        // 针对startup对象进行switch case 选择
        switch (startOpt) {
        case FORMAT:
          // 格式化
          // 安装hadoop后第一次启动之前要执行的格式化命令 hadoop namenode -format
          aborted = format(conf, startOpt.getForceFormat(),
              startOpt.getInteractiveFormat());
          terminate(aborted ? 1 : 0);
          return null; // avoid javac warning
        case GENCLUSTERID:
          String clusterID = NNStorage.newClusterID();
          LOG.info("Generated new cluster id: {}", clusterID);
          terminate(0);
          return null;
        case ROLLBACK:
          aborted = doRollback(conf, true);
          terminate(aborted ? 1 : 0);
          return null; // avoid warning
        case BOOTSTRAPSTANDBY:
          String[] toolArgs = Arrays.copyOfRange(argv, 1, argv.length);
          int rc = BootstrapStandby.run(toolArgs, conf);
          terminate(rc);
          return null; // avoid warning
        case INITIALIZESHAREDEDITS:
          aborted = initializeSharedEdits(conf,
              startOpt.getForceFormat(),
              startOpt.getInteractiveFormat());
          terminate(aborted ? 1 : 0);
          return null; // avoid warning
        case BACKUP:
        case CHECKPOINT:
          NamenodeRole role = startOpt.toNodeRole();
          DefaultMetricsSystem.initialize(role.toString().replace(" ", ""));
          return new BackupNode(conf, role);
        case RECOVER:
          NameNode.doRecovery(startOpt, conf);
          return null;
        case METADATAVERSION:
          printMetadataVersion(conf);
          terminate(0);
          return null; // avoid javac warning
        case UPGRADEONLY:
          DefaultMetricsSystem.initialize("NameNode");
          new NameNode(conf);
          terminate(0);
          return null;
        default:
          // 正常启动的时候就会走到这里
          // metrics:度量系统记录详细运行信息
          DefaultMetricsSystem.initialize("NameNode");
          // 初始化NameNode
          return new NameNode(conf);
        }
      }
    

    NameNode构造方法

    	public NameNode(Configuration conf) throws IOException {
        // 默认为正常的NameNode
        this(conf, NamenodeRole.NAMENODE);
      }
    
      protected NameNode(Configuration conf, NamenodeRole role)
          throws IOException {
        // 将配置文件赋值到父类的静态变量中
        super(conf);
        // 初始化Tracer
        // 在“进程”中使用一个Tracer实例来收集和分发它的跟踪范围。Tracer实例是所有跟踪的一站式商店。
        this.tracer = new Tracer.Builder("NameNode").
            conf(TraceUtils.wrapHadoopConf(NAMENODE_HTRACE_PREFIX, conf)).
            build();
        // TracerConfigurationManager类提供了通过RPC协议在运行时管理跟踪器配置的函数。
        this.tracerConfigurationManager =
            new TracerConfigurationManager(NAMENODE_HTRACE_PREFIX, conf);
        this.role = role;
        // clientNamenodeAddress : 客户端将用来访问这个namenode或名称服务的namenode地址。对于使用逻辑URI的HA配置,它将是逻辑地址。
        String nsId = getNameServiceId(conf);
        String namenodeId = HAUtil.getNameNodeId(conf, nsId);
        clientNamenodeAddress = NameNodeUtils.getClientNamenodeAddress(
            conf, nsId);
        // 虚拟机中搭建集群启动日志打印为:
        // 2021-07-07 17:45:46,560 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: Clients are to use wanglj01:9000 to access this namenode/service.
        if (clientNamenodeAddress != null) {
          LOG.info("Clients should use {} to access"
              + " this namenode/service.", clientNamenodeAddress);
        }
        // ha集群相关
        this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
        state = createHAState(getStartupOption(conf));
        this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
        this.haContext = createHAContext();
        try {
          // 给联邦模式下准备的,主要是设置联邦模式下namenode的地址和RPC地址
          initializeGenericKeys(conf, nsId, namenodeId);
          // 初始化namenode的核心方法
          initialize(getConf());
          // HA相关内容
          state.prepareToEnterState(haContext);
          try {
            haContext.writeLock();
            state.enterState(haContext);
          } finally {
            haContext.writeUnlock();
          }
        } catch (IOException e) {
          this.stopAtException(e);
          throw e;
        } catch (HadoopIllegalArgumentException e) {
          this.stopAtException(e);
          throw e;
        }
        // 启动成功
        notBecomeActiveInSafemode = conf.getBoolean(
            DFS_HA_NN_NOT_BECOME_ACTIVE_IN_SAFEMODE,
            DFS_HA_NN_NOT_BECOME_ACTIVE_IN_SAFEMODE_DEFAULT);
        this.started.set(true);
      }
    

    NameNode#initialize

      protected void initialize(Configuration conf) throws IOException {
        if (conf.get(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS) == null) {
          String intervals = conf.get(DFS_METRICS_PERCENTILES_INTERVALS_KEY);
          if (intervals != null) {
            conf.set(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS,
              intervals);
          }
        }
        // UserGroupInformation类作用:
        // Hadoop的用户和组信息。该类封装了一个JAAS Subject,并提供了确定用户用户名和组的方法。它同时支持Windows、Unix和Kerberos登录模块。
        // 方法简介:设置UGI的静态配置。特别是设置安全身份验证机制和组查找服务。
        UserGroupInformation.setConfiguration(conf);
        // 以NameNode配置的用户登录。
        loginAsNameNodeUser(conf);
        // 初始化namemode的度量系统
        NameNode.initMetrics(conf, this.getRole());
        StartupProgressMetrics.register(startupProgress);
        // 初始化jvm监听的度量系统
        // JvmPauseMonitor类的作用:
        // 此类建立一个简单的线程。在此线程中,在循环中运行sleep一段时间方法,如果sleep花费的时间比传递给sleep方法的时间长,
        // 就意味着JVM或者宿主机已经出现了停顿处理现象,可能会导致其它问题,如果这种停顿被监测出来,线程会打印一个消息。
        pauseMonitor = new JvmPauseMonitor();
        pauseMonitor.init(conf);
        pauseMonitor.start();
        metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
    
        if (conf.getBoolean(DFS_NAMENODE_GC_TIME_MONITOR_ENABLE,
            DFS_NAMENODE_GC_TIME_MONITOR_ENABLE_DEFAULT)) {
          long observationWindow = conf.getTimeDuration(
              DFS_NAMENODE_GC_TIME_MONITOR_OBSERVATION_WINDOW_MS,
              DFS_NAMENODE_GC_TIME_MONITOR_OBSERVATION_WINDOW_MS_DEFAULT,
              TimeUnit.MILLISECONDS);
          long sleepInterval = conf.getTimeDuration(
              DFS_NAMENODE_GC_TIME_MONITOR_SLEEP_INTERVAL_MS,
              DFS_NAMENODE_GC_TIME_MONITOR_SLEEP_INTERVAL_MS_DEFAULT,
              TimeUnit.MILLISECONDS);
          gcTimeMonitor = new Builder().observationWindowMs(observationWindow)
              .sleepIntervalMs(sleepInterval).build();
          gcTimeMonitor.start();
          metrics.getJvmMetrics().setGcTimeMonitor(gcTimeMonitor);
        }
        // 启动httpserver
        if (NamenodeRole.NAMENODE == role) {
          startHttpServer(conf);
        }
        // 启动nameNode时从磁盘加载fsimage以及edits文件,初始化FsNamesystem、FsDirectory、 LeaseManager等
        loadNamesystem(conf);
        startAliasMapServerIfNecessary(conf);
        // 创建rpcserver,支持namenode与datanode,client进行通信的协议
        // 封装了NameNodeRpcServer clientRpcServer,支持 ClientNamenodeProtocol、DatanodeProtocolPB等协议
        // 啥是rpc看这里:https://www.zhihu.com/question/25536695
        rpcServer = createRpcServer(conf);
    
        initReconfigurableBackoffKey();
    
        if (clientNamenodeAddress == null) {
          // This is expected for MiniDFSCluster. Set it now using
          // the RPC server's bind address.
          clientNamenodeAddress = 
              NetUtils.getHostPortString(getNameNodeAddress());
          LOG.info("Clients are to use " + clientNamenodeAddress + " to access"
              + " this namenode/service.");
        }
        if (NamenodeRole.NAMENODE == role) {
          httpServer.setNameNodeAddress(getNameNodeAddress());
          httpServer.setFSImage(getFSImage());
          if (levelDBAliasMapServer != null) {
            httpServer.setAliasMap(levelDBAliasMapServer.getAliasMap());
          }
        }
        // 启动常用到主备状态的服务
        startCommonServices(conf);
        // 启动一个计时器,定期将NameNode度量写入日志文件。可以通过配置禁用此行为。
        startMetricsLogger(conf);
      }
    

    NameNode#startCommonServices

      /** Start the services common to active and standby states */
      private void startCommonServices(Configuration conf) throws IOException {
        // 创建NameNodeResourceChecker、激活BlockManager等
        namesystem.startCommonServices(conf, haContext);
        registerNNSMXBean();
        // 非NamenodeRole.NAMENODE的角色在此处启动HttpServer
        if (NamenodeRole.NAMENODE != role) {
          startHttpServer(conf);
          httpServer.setNameNodeAddress(getNameNodeAddress());
          httpServer.setFSImage(getFSImage());
          if (levelDBAliasMapServer != null) {
            httpServer.setAliasMap(levelDBAliasMapServer.getAliasMap());
          }
        }
        // 启动RPCServer
        rpcServer.start();
        // 启动各插件
        try {
          plugins = conf.getInstances(DFS_NAMENODE_PLUGINS_KEY,
              ServicePlugin.class);
        } catch (RuntimeException e) {
          String pluginsValue = conf.get(DFS_NAMENODE_PLUGINS_KEY);
          LOG.error("Unable to load NameNode plugins. Specified list of plugins: " +
              pluginsValue, e);
          throw e;
        }
        for (ServicePlugin p: plugins) {
          try {
            p.start(this);
          } catch (Throwable t) {
            LOG.warn("ServicePlugin " + p + " could not be started", t);
          }
        }
        LOG.info(getRole() + " RPC up at: " + getNameNodeAddress());
        if (rpcServer.getServiceRpcAddress() != null) {
          LOG.info(getRole() + " service RPC up at: "
              + rpcServer.getServiceRpcAddress());
        }
      }
    

    FSNamesystem#startCommonServices

    方法用于启动对主备状态都通用的服务:

      void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
        this.registerMBean(); // register the MBean for the FSNamesystemState
        writeLock();
        this.haContext = haContext;
        try {
          // 创建NameNodeResourceChecker(资源检查线程),并立即检查一次
          nnResourceChecker = new NameNodeResourceChecker(conf);
          checkAvailableResources();
          assert !blockManager.isPopulatingReplQueues();
          // 设置一些启动过程中的信息
          StartupProgress prog = NameNode.getStartupProgress();
          prog.beginPhase(Phase.SAFEMODE);
          long completeBlocksTotal = getCompleteBlocksTotal();
          prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
              completeBlocksTotal);
          // 激活blockManager: 保存与存储在Hadoop集群中的块相关的信息。
          blockManager.activate(conf, completeBlocksTotal);
        } finally {
          writeUnlock("startCommonServices");
        }
        
        registerMXBean();
        DefaultMetricsSystem.instance().register(this);
        if (inodeAttributeProvider != null) {
          inodeAttributeProvider.start();
          dir.setINodeAttributeProvider(inodeAttributeProvider);
        }
        snapshotManager.registerMXBean();
        InetSocketAddress serviceAddress = NameNode.getServiceAddress(conf, true);
        this.nameNodeHostName = (serviceAddress != null) ?
            serviceAddress.getHostName() : "";
      }
      
    

    BlockManager#activate

      public void activate(Configuration conf, long blockTotal) {
        // 启动PendingReplicationBlocks,这个类主要是对数据块进行一些记账工作。类似于Block可能存放在那个Datanode上这种。
        pendingReconstruction.start();
        // 激活DatanodeManager:启动DecommissionManager--Monitor、HeartbeatManager-- Monitor
        datanodeManager.activate(conf);
        // 启动redundancyThread, 大致作用是:
        // 计算可以在数据节点上调度的块复制和块失效工作。datanode将在下一个心跳时被告知这项工作
        // 如果有任何重构请求超时,获取它们并将它们放回需要的重构队列中
        // 重新扫描之前推迟的块列表
        this.redundancyThread.setName("RedundancyMonitor");
        this.redundancyThread.start();
        // 启动storageInfoDefragmenterThread
        // 它监视StorageInfo TreeSet的碎片,并在它低于某个阈值时压缩它。
        storageInfoDefragmenterThread.setName("StorageInfoMonitor");
        storageInfoDefragmenterThread.start();
        //块汇报线程穹顶(心跳检测机制)
        this.blockReportThread.start();
        mxBeanName = MBeans.register("NameNode", "BlockStats", this);
        // 初始化安全模式
        bmSafeMode.activate(blockTotal);
      }
    

    namenode的主要责任是文件元信息与数据块映射的管理。相应的,namenode的启动流程需要关注

    与客户端、datanode通信的工作线程,文件元信息的管理机制,数据块的管理机制等。其中,

    RpcServer主要负责与客户端、datanode通信,FSDirectory主要负责管理文件元信息。

  • 相关阅读:
    【python笔记】类
    【Marva Collins' Way】第八章
    【Marva Collins' Way】第七章
    【python笔记】包
    【python笔记】模块
    【Marva Collins' Way】第六章
    【Marva Collins' Way】第九章
    【python笔记】异常
    Axios跨域&封装接口
    js更新数据
  • 原文地址:https://www.cnblogs.com/zuojing/p/14990538.html
Copyright © 2020-2023  润新知