Hadoop源码分析(11)

初始化editlog

  在文档(10)中解析了FSImage类的loadFSImage方法, 这个方法主要用来加载元数据。它大致可以分为5个部分:是查找fsimage文件; 初始化editlog;加载editlog流;加载fsimage文件;执行editlog。

  这里继续分析初始化editlog的initEditLog方法,其内容如下:

public void initEditLog(StartupOption startOpt) throws IOException {
    Preconditions.checkState(getNamespaceID() != 0,
        "Must know namespace ID before initting edit log");
    String nameserviceId = DFSUtil.getNamenodeNameServiceId(conf);
    if (!HAUtil.isHAEnabled(conf, nameserviceId)) {
      // If this NN is not HA
      editLog.initJournalsForWrite();
      editLog.recoverUnclosedStreams();
    } else if (HAUtil.isHAEnabled(conf, nameserviceId)
        && (startOpt == StartupOption.UPGRADE
            || startOpt == StartupOption.UPGRADEONLY
            || RollingUpgradeStartupOption.ROLLBACK.matches(startOpt))) {
      // This NN is HA, but we're doing an upgrade or a rollback of rolling
      // upgrade so init the edit log for write.
      editLog.initJournalsForWrite();
      if (startOpt == StartupOption.UPGRADE
          || startOpt == StartupOption.UPGRADEONLY) {
        long sharedLogCTime = editLog.getSharedLogCTime();
        if (this.storage.getCTime() < sharedLogCTime) {
          throw new IOException("It looks like the shared log is already " +
              "being upgraded but this NN has not been upgraded yet. You " +
              "should restart this NameNode with the '" +
              StartupOption.BOOTSTRAPSTANDBY.getName() + "' option to bring " +
              "this NN in sync with the other.");
        }
      }
      editLog.recoverUnclosedStreams();
    } else {
      // This NN is HA and we're not doing an upgrade.
      editLog.initSharedJournalsForRead();
    }
  }

  这个方法很简单,总体来说就是一个if语句。这个语句是用于处理 不同集群状态,首先是第5行的if语句,这是用来处理非HA状态的集群的情况;然 后是第9行的else if语句,这里是用来处理HA状态但集群正在更新的情况;最后 是第28行的else语句,这里是用来处理HA状态的集群的。

  按照之前的配置,集群是HA状态并且没有更新,所以会执行第28行的语句。 这个else语句内部就一句:调用了editLog的initSharedJournalsForRead 方法。editLog是在FSImage对象创建的时候创建的一个类。

创建FSImage对象

  如上图,在创建的editLog的类为FSEditLog,创建的时候传入了 三个参数,第一个是conf,代表了hdfs的配置文件;第二个是storage, 文档(10)解析过;第三个是editsDirs,这个参数传入FSImage的第三个参数, 这个参数在文档(8)中解析过。

  FSEditLog的构造方法如下:

FSEditLog(Configuration conf, NNStorage storage, List<URI> editsDirs) {
    isSyncRunning = false;
    this.conf = conf;
    this.storage = storage;
    metrics = NameNode.getNameNodeMetrics();
    lastPrintTime = monotonicNow();

    // If this list is empty, an error will be thrown on first use
    // of the editlog, as no journals will exist
    this.editsDirs = Lists.newArrayList(editsDirs);

    this.sharedEditsDirs = FSNamesystem.getSharedEditsDirs(conf);
  }

  这个方法只是一些参数赋值而已。重点是最后的两个参数: editsDirs和sharedEditsDirs。editsDirs是传入的参数,它代表着本地 的editlog和远端的editlog。而sharedEditsDirs只代表着远端的editlog。

  接着继续分析上文提到的initSharedJournalsForRead方法,其内容如下:

public synchronized void initSharedJournalsForRead() {
    if (state == State.OPEN_FOR_READING) {
      LOG.warn("Initializing shared journals for READ, already open for READ",
          new Exception());
      return;
    }
    Preconditions.checkState(state == State.UNINITIALIZED ||
        state == State.CLOSED);

    initJournals(this.sharedEditsDirs);
    state = State.OPEN_FOR_READING;
  }

  这里重点就一个:第10行的initJournals方法。这里这个方法 传入的是sharedEditsDirs。上文解析过这个参数是代表着远端的edit目录。 注意这里因为是启动,所有的namenode都是以standby状态启动,所以这里的 edit目录只有远端目录。在standby状态转换成active状态的时候,会重新初 始化editlog,这时的目录有本地的与远端的两个目录。

  initJournals方法的内容如下:

 private synchronized void initJournals(List<URI> dirs) {
    int minimumRedundantJournals = conf.getInt(
        DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_KEY,
        DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_DEFAULT);

    synchronized(journalSetLock) {
      journalSet = new JournalSet(minimumRedundantJournals);

      for (URI u : dirs) {
        boolean required = FSNamesystem.getRequiredNamespaceEditsDirs(conf)
            .contains(u);
        if (u.getScheme().equals(NNStorage.LOCAL_URI_SCHEME)) {
          StorageDirectory sd = storage.getStorageDirectory(u);
          if (sd != null) {
            journalSet.add(new FileJournalManager(conf, sd, storage),
                required, sharedEditsDirs.contains(u));
          }
        } else {
          journalSet.add(createJournal(u), required,
              sharedEditsDirs.contains(u));
        }
      }
    }

    if (journalSet.isEmpty()) {
      LOG.error("No edits directories configured!");
    } 
  }

  首先是第7行,这里会创建一个JournalSet对象。然后遍历传入的 dirs,然后根据其类型来做不同的操作。如果是本地的editlog则执行第12行if 语句内的内容,如果是远端,则执行第18行的else语句内的内容。

  在上文的分析中,提到了传入的参数代表着远端的editlog。 所以在会执行第18行的语句。这里就一句代码,使用journalset的add方法, 这个方法传入了三个参数:第一个是createJournal方法的返回值,第二个 是required,它是上文从创建的Boolean值,第三个也是一个boolean值。 其中重点是第一个参数,其调用的createJournal方法如下:

private JournalManager createJournal(URI uri) {
    Class<? extends JournalManager> clazz
      = getJournalClass(conf, uri.getScheme());

    try {
      Constructor<? extends JournalManager> cons
        = clazz.getConstructor(Configuration.class, URI.class,
            NamespaceInfo.class);
      return cons.newInstance(conf, uri, storage.getNamespaceInfo());
    } catch (Exception e) {
      throw new IllegalArgumentException("Unable to construct journal, "
                                         + uri, e);
    }
  }

  这个方法是用来创建JournalManager对象的,这里是利用java 的反射机制来创建对象。创建的类根据其获取的class来决定。

  这里的class是通过getJournalClass方法来获取的, 这个方法的内容如下:

static Class<? extends JournalManager> getJournalClass(Configuration conf,
                               String uriScheme) {
    String key
      = DFSConfigKeys.DFS_NAMENODE_EDITS_PLUGIN_PREFIX + "." + uriScheme;
    Class <? extends JournalManager> clazz = null;
    try {
      clazz = conf.getClass(key, null, JournalManager.class);
    } catch (RuntimeException re) {
      throw new IllegalArgumentException(
          "Invalid class specified for " + uriScheme, re);
    }

    if (clazz == null) {
      LOG.warn("No class configured for " +uriScheme
               + ", " + key + " is empty");
      throw new IllegalArgumentException(
          "No class configured for " + uriScheme);
    }
    return clazz;
  }

  这段代码的重点在第7行,这里调用的了conf的getClass方法, 这里传入的key定义在第3行是由DFS_NAMENODE_EDITS_PLUGIN_PREFIX和 uriScheme来确定的,其中DFS_NAMENODE_EDITS_PLUGIN_PREFIX的定义如下:

public static final String  DFS_NAMENODE_EDITS_PLUGIN_PREFIX = "dfs.namenode.edits.journal-plugin";

  uriScheme是由传入的uri来确定的,这里的值为qjournal。所以 这里的生成的key为dfs.namenode.edits.journal-plugin.qjournal。

  getClass方法内容如下:

public <U> Class<? extends U> getClass(String name, 
                                         Class<? extends U> defaultValue, 
                                         Class<U> xface) {
    try {
      Class<?> theClass = getClass(name, defaultValue);
      if (theClass != null && !xface.isAssignableFrom(theClass))
        throw new RuntimeException(theClass+" not "+xface.getName());
      else if (theClass != null)
        return theClass.asSubclass(xface);
      else
        return null;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public Class<?> getClass(String name, Class<?> defaultValue) {
    String valueString = getTrimmed(name);
    if (valueString == null)
      return defaultValue;
    try {
      return getClassByName(valueString);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  这里可以看见getClass有一个重载方法,最终getClass方法会 调用getTrimmed方法(第18行),从配置文件中获取数据,若返回值为空则返回 传入的默认值;不为空则返回获取的值。

  getTrimmed方法内容如下:

 public String getTrimmed(String name) {
    String value = get(name);

    if (null == value) {
      return null;
    } else {
      return value.trim();
    }
  }

  重点在第2行,这里会调用get方法从配置文件中获取值。 这个方法在文档(7)中解析过了。它主要是从configuration对象的 properties中获取数据,而properties中值是从配置文档中加载出来的。 这里需要获取的neme,上文解析了是 dfs.namenode.edits.journal-plugin.qjournal。这个参数配置 在hdfs-default.xml中,其内容如下:

<property>
  <name>dfs.namenode.edits.journal-plugin.qjournal</name>
  <value>org.apache.hadoop.hdfs.qjournal.client.QuorumJournalManager</value>
</property>

  这里配置的类为QuorumJournalManager,所以在 createJournal方法中会创建一个这个类的对象来处理远端的edit日志。

  QuorumJournalManager的构造方法如下:

public QuorumJournalManager(Configuration conf,
      URI uri, NamespaceInfo nsInfo) throws IOException {
    this(conf, uri, nsInfo, IPCLoggerChannel.FACTORY);
  }

  QuorumJournalManager(Configuration conf,
      URI uri, NamespaceInfo nsInfo,
      AsyncLogger.Factory loggerFactory) throws IOException {
    Preconditions.checkArgument(conf != null, "must be configured");

    this.conf = conf;
    this.uri = uri;
    this.nsInfo = nsInfo;
    this.loggers = new AsyncLoggerSet(createLoggers(loggerFactory));
    this.connectionFactory = URLConnectionFactory
        .newDefaultURLConnectionFactory(conf);

    // Configure timeouts.
    this.startSegmentTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_START_SEGMENT_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_START_SEGMENT_TIMEOUT_DEFAULT);
    this.prepareRecoveryTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_PREPARE_RECOVERY_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_PREPARE_RECOVERY_TIMEOUT_DEFAULT);
    this.acceptRecoveryTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_ACCEPT_RECOVERY_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_ACCEPT_RECOVERY_TIMEOUT_DEFAULT);
    this.finalizeSegmentTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_FINALIZE_SEGMENT_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_FINALIZE_SEGMENT_TIMEOUT_DEFAULT);
    this.selectInputStreamsTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_SELECT_INPUT_STREAMS_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_SELECT_INPUT_STREAMS_TIMEOUT_DEFAULT);
    this.getJournalStateTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_GET_JOURNAL_STATE_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_GET_JOURNAL_STATE_TIMEOUT_DEFAULT);
    this.newEpochTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_NEW_EPOCH_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_NEW_EPOCH_TIMEOUT_DEFAULT);
    this.writeTxnsTimeoutMs = conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_WRITE_TXNS_TIMEOUT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_WRITE_TXNS_TIMEOUT_DEFAULT);
  }

  这个方法也有一个重载方法,方法内都是一些参数赋值操作。 其中重点在于第14行为loggers赋值。这个参数是实际用来管理editlog的对象。 它的类型为AsyncLoggerSet,在创建这个对象时需要传入一个参数, 这个参数会通过createLoggers方法来创建。而createLoggers方法又传入了 一个参数loggerFactory,这个参数是方法的传入值。这里的传入值是第3行的 IPCLoggerChannel.FACTORY,其内容如下:

  static final Factory FACTORY = new AsyncLogger.Factory() {
    @Override
    public AsyncLogger createLogger(Configuration conf, NamespaceInfo nsInfo,
        String journalId, InetSocketAddress addr) {
      return new IPCLoggerChannel(conf, nsInfo, journalId, addr);
    }
  };

  这里是一个匿名内部类,它实现该接口的createLogger方法, 这个方法也很简单就是创建了一个IPCLoggerChannel对象。

  然后是createLoggers方法,这个方法内容如下:

protected List<AsyncLogger> createLoggers(
      AsyncLogger.Factory factory) throws IOException {
    return createLoggers(conf, uri, nsInfo, factory);
  }
  
    static List<AsyncLogger> createLoggers(Configuration conf,
      URI uri, NamespaceInfo nsInfo, AsyncLogger.Factory factory)
          throws IOException {
    List<AsyncLogger> ret = Lists.newArrayList();
    List<InetSocketAddress> addrs = getLoggerAddresses(uri);
    String jid = parseJournalId(uri);
    for (InetSocketAddress addr : addrs) {
      ret.add(factory.createLogger(conf, nsInfo, jid, addr));
    }
    return ret;
  }

  这也是一个重载方法,重点在第10行,这里会调用 getLoggerAddresses方法将传入的uri解析成对应的ip与地址。 在之前提到这里传入的是代表远端的editlog,它在配置文件中的内容如下:

journalnode配置

  这里配置了三个节点ip与端口,这三个节点是配置的journalnode 节点。getLoggerAddresses方法会将这个字符串解析出各个节点的ip地址。

  然后再看createLoggers方法的第12行,这里会遍历上面解析出 来的ip地址,然后利用传入的factory,创建与单个journalnode连接的对象。 这里的factory上文解析了,其实际会创建一个IPCLoggerChannel对象。

  IPCLoggerChannel对象的构造方法如下:

public IPCLoggerChannel(Configuration conf,
      NamespaceInfo nsInfo,
      String journalId,
      InetSocketAddress addr) {
    this.conf = conf;
    this.nsInfo = nsInfo;
    this.journalId = journalId;
    this.addr = addr;

    this.queueSizeLimitBytes = 1024 * 1024 * conf.getInt(
        DFSConfigKeys.DFS_QJOURNAL_QUEUE_SIZE_LIMIT_KEY,
        DFSConfigKeys.DFS_QJOURNAL_QUEUE_SIZE_LIMIT_DEFAULT);

    singleThreadExecutor = MoreExecutors.listeningDecorator(
        createSingleThreadExecutor());
    parallelExecutor = MoreExecutors.listeningDecorator(
        createParallelExecutor());

    metrics = IPCLoggerChannelMetrics.create(this);
  }

  这个方法主要是一些赋值操作。

  自此,QuorumJournalManager类中的loggers便创建完成了。 这个loggers本身是一个AsyncLoggerSet,这个对象里主要存储了与每个 journalnode对应的IPCLoggerChannel对象。 namenode主要通过这个AsyncLoggerSet和其内部存储的IPCLoggerChannel 来与journalnode进行连接通信。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值