Hadoop源码分析(20)

Hadoop源码分析(20)

1、 namenode读取journalnode数据

  在文档(12)中分析到了namenode加载元数据的时候分为两部分:editlog和FSImage。在加载editlog的时候,由于配置了HA模式,namenode需要从journalnode的中读取editlog。于是文档(12)分析了namenode读取journalnode的日志的方法:selectInputStreams方法。这个方法会先调用一个getEditLogManifest方法获取journalnode中的日志列表。而这个getEditLogManifest方法是实际是一个远程调用方法。于是文档(13)到文档(19)中解析HDFS的远程调用的实现方式。

  在解析完远程调用之后,这里继续回到读取journalnode日志的方法来,在文档(12)中namenode端最后会通过IPCLoggerChannel对象的方法创建一个代理对象,通过代理对象将getEditLogManifest方法发送给journalnode,在文档(19)中分析了通过远程调用发送的方法都会调用service的callBlockingMethod方法来执行,这个方法的内容如下:

public final com.google.protobuf.Message callBlockingMethod(
            com.google.protobuf.Descriptors.MethodDescriptor method,
            com.google.protobuf.RpcController controller,
            com.google.protobuf.Message request)
            throws com.google.protobuf.ServiceException {
          if (method.getService() != getDescriptor()) {
            throw new java.lang.IllegalArgumentException(
              "Service.callBlockingMethod() given method descriptor for " +
              "wrong service type.");
          }
          switch(method.getIndex()) {
            case 0:
              return impl.isFormatted(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.IsFormattedRequestProto)request);
            case 1:
              return impl.discardSegments(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.DiscardSegmentsRequestProto)request);
            case 2:
              return impl.getJournalCTime(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.GetJournalCTimeRequestProto)request);
            case 3:
              return impl.doPreUpgrade(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.DoPreUpgradeRequestProto)request);
            case 4:
              return impl.doUpgrade(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.DoUpgradeRequestProto)request);
            case 5:
              return impl.doFinalize(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.DoFinalizeRequestProto)request);
            case 6:
              return impl.canRollBack(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.CanRollBackRequestProto)request);
            case 7:
              return impl.doRollback(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.DoRollbackRequestProto)request);
            case 8:
              return impl.getJournalState(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.GetJournalStateRequestProto)request);
            case 9:
              return impl.newEpoch(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.NewEpochRequestProto)request);
            case 10:
              return impl.format(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.FormatRequestProto)request);
            case 11:
              return impl.journal(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.JournalRequestProto)request);
            case 12:
              return impl.heartbeat(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.HeartbeatRequestProto)request);
            case 13:
              return impl.startLogSegment(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.StartLogSegmentRequestProto)request);
            case 14:
              return impl.finalizeLogSegment(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.FinalizeLogSegmentRequestProto)request);
            case 15:
              return impl.purgeLogs(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.PurgeLogsRequestProto)request);
            case 16:
              return impl.getEditLogManifest(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.GetEditLogManifestRequestProto)request);
            case 17:
              return impl.prepareRecovery(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.PrepareRecoveryRequestProto)request);
            case 18:
              return impl.acceptRecovery(controller, (org.apache.hadoop.hdfs.qjournal.protocol.QJournalProtocolProtos.AcceptRecoveryRequestProto)request);
            default:
              throw new java.lang.AssertionError("Can't get here.");
          }
        }

  重点在第45行到这里是在处理上文说的getEditLogManifest方法,它实际是调用的impl的对象的方法。而这里的impl在文档(19)中分析过是方法传入的对象,其调用的方法内容如下:

@Override
  public GetEditLogManifestResponseProto getEditLogManifest(
      RpcController controller, GetEditLogManifestRequestProto request)
      throws ServiceException {
    try {
      return impl.getEditLogManifest(
          request.getJid().getIdentifier(),
          request.getSinceTxId(),
          request.getInProgressOk());
    } catch (IOException e) {
      throw new ServiceException(e);
    }
  }

  这里重点是第6行,这里又调用impl的getEditLogManifest方法。这里的impl是JournalNodeRpcServer类的对象,其getEditLogManifest方法如下:

public GetEditLogManifestResponseProto getEditLogManifest(String jid,
      long sinceTxId, boolean inProgressOk)
      throws IOException {

    RemoteEditLogManifest manifest = jn.getOrCreateJournal(jid)
        .getEditLogManifest(sinceTxId, inProgressOk);

    return GetEditLogManifestResponseProto.newBuilder()
        .setManifest(PBHelper.convert(manifest))
        .setHttpPort(jn.getBoundHttpAddress().getPort())
        .setFromURL(jn.getHttpServerURI())
        .build();
  }

  重点在第5行这里会先获取journal对象然后使用journal对象获取editlog日志列表,然后是第8行将获取的日志列表封装并返回。

  这里先分析获取journal对象的getOrCreateJournal方法,该方法内容如下:

synchronized Journal getOrCreateJournal(String jid, StartupOption startOpt)
      throws IOException {
    QuorumJournalManager.checkJournalId(jid);

    Journal journal = journalsById.get(jid);
    if (journal == null) {
      File logDir = getLogDir(jid);
      LOG.info("Initializing journal in directory " + logDir);      
      journal = new Journal(conf, logDir, jid, startOpt, new ErrorReporter());
      journalsById.put(jid, journal);
    }

    return journal;
  }

  这个方法也是用的HDFS常用方式,首先是第5行先从journalsById中获取journal,这里的journalsById是一个map;然后是第6行如果map中没有该journal,则新建一个;然后是7行根据jid获取一个日志目录;然后是第9行,根据目录和其他信息创建journal对象;然后将创建的journal添加到journalsById中,最后再返回journal。

  第7行的getLogDir方法内容如下:

  private File getLogDir(String jid) {
    String dir = conf.get(DFSConfigKeys.DFS_JOURNALNODE_EDITS_DIR_KEY,
        DFSConfigKeys.DFS_JOURNALNODE_EDITS_DIR_DEFAULT);
    Preconditions.checkArgument(jid != null &&
        !jid.isEmpty(),
        "bad journal identifier: %s", jid);
    assert jid != null;
    return new File(new File(dir), jid);
  }

 这段代码很简单就是从配置文件中获取dir的值,然后根据dir的值来创建File对象。其中从配置文件中读取的两个值内容如下:

public static final String  DFS_JOURNALNODE_EDITS_DIR_KEY = "dfs.journalnode.edits.dir";
public static final String  DFS_JOURNALNODE_EDITS_DIR_DEFAULT = "/tmp/hadoop/dfs/journalnode/";

  这里读取的key的值为dfs.journalnode.edits.dir,设置的默认值为/tmp/hadoop/dfs/journalnode/。这个key在文档(2)的安装教程中修改的配置文件中有配置。这个配置是用来指定journalnode存储日志的目录路径的配置。

  然后是journal类的构造方法,其内容如下:

Journal(Configuration conf, File logDir, String journalId,
      StartupOption startOpt, StorageErrorReporter errorReporter)
      throws IOException {
    storage = new JNStorage(conf, logDir, startOpt, errorReporter);
    this.journalId = journalId;

    refreshCachedData();

    this.fjm = storage.getJournalManager();

    this.metrics = JournalMetrics.create(this);

    EditLogFile latest = scanStorageForLatestEdits();
    if (latest != null) {
      highestWrittenTxId = latest.getLastTxId();
    }
  } 

  这里首先是第4行创建一个JNStorage类的对象,然后是第9行通过上面的对象来获得journalmanager,最后是第13行扫描存储目录获取最新的日志文件,然后获取其txid。

  其中JNStorage的构造方法如下:

  protected JNStorage(Configuration conf, File logDir, StartupOption startOpt,
      StorageErrorReporter errorReporter) throws IOException {
    super(NodeType.JOURNAL_NODE);

    sd = new StorageDirectory(logDir);
    this.addStorageDir(sd);
    this.fjm = new FileJournalManager(conf, sd, errorReporter);

    analyzeAndRecoverStorage(startOpt);
  }

  这里会通过上文解析的dir创建一个sd,然后再通过sd创建一个fjm对象。

  然后再解析获取到journal对象后调用的getEditLogManifest方法,这个方法内容如下:

 public RemoteEditLogManifest getEditLogManifest(long sinceTxId,
      boolean inProgressOk) throws IOException {
    // No need to checkRequest() here - anyone may ask for the list
    // of segments.
    checkFormatted();

    List<RemoteEditLog> logs = fjm.getRemoteEditLogs(sinceTxId, inProgressOk);

    if (inProgressOk) {
      RemoteEditLog log = null;
      for (Iterator<RemoteEditLog> iter = logs.iterator(); iter.hasNext();) {
        log = iter.next();
        if (log.isInProgress()) {
          iter.remove();
          break;
        }
      }
      if (log != null && log.isInProgress()) {
        logs.add(new RemoteEditLog(log.getStartTxId(), getHighestWrittenTxId(),
            true));
      }
    }

    return new RemoteEditLogManifest(logs);
  }

  这里的重点在第7行,调用fjm的getRemoteEditLogs方法来获取日志文件列表。然后是第9行到第22行,这里在处理inProgress的日志,最后是第24行返回获取到的日志列表。

  首先是getRemoteEditLogs方法,其内容如下:

  public List<RemoteEditLog> getRemoteEditLogs(long firstTxId,
      boolean inProgressOk) throws IOException {
    File currentDir = sd.getCurrentDir();
    List<EditLogFile> allLogFiles = matchEditLogs(currentDir);
    List<RemoteEditLog> ret = Lists.newArrayListWithCapacity(
        allLogFiles.size());
    for (EditLogFile elf : allLogFiles) {
      if (elf.hasCorruptHeader() || (!inProgressOk && elf.isInProgress())) {
        continue;
      }
      if (elf.isInProgress()) {
        try {
          elf.validateLog();
        } catch (IOException e) {
          LOG.error("got IOException while trying to validate header of " +
              elf + ".  Skipping.", e);
          continue;
        }
      }
      if (elf.getFirstTxId() >= firstTxId) {
        ret.add(new RemoteEditLog(elf.firstTxId, elf.lastTxId,
            elf.isInProgress()));
      } else if (elf.getFirstTxId() < firstTxId && firstTxId <= elf.getLastTxId()) {
        // If the firstTxId is in the middle of an edit log segment. Return this
        // anyway and let the caller figure out whether it wants to use it.
        ret.add(new RemoteEditLog(elf.firstTxId, elf.lastTxId,
            elf.isInProgress()));
      }
    }

    Collections.sort(ret);

    return ret;
  }

  首先是第3行,拿到存储editlog的目录,然后第4行从目录里获取到所有的editlog文件。然后是第7行使用for循环遍历拿到的所有editlog文件,然后是第20行,如果这个文件的txid大于指定的txid则将该文件添加到ret中。最后再返回这个ret。

  然后是inProgress日志,在之前解析editlog的时候提到过,editlog并不是一个日志文件,而多个日志文件,日志文件间通过txid来进行区分,如下图:

editlog文件目录1

  但是这些日志文件是已经完成的日志文件,如果是最新的正在写入日志的文件,则是以下情况:

editlog文件目录2

  inprogress日志便表示这种情况,在分析那么弄得加载元数据的时候,加载的元数据不会包含inprogress日志。这个日志的数据会由active的namenode负责加载(namenode都是以standby启动)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值