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来进行区分,如下图:
但是这些日志文件是已经完成的日志文件,如果是最新的正在写入日志的文件,则是以下情况:
inprogress日志便表示这种情况,在分析那么弄得加载元数据的时候,加载的元数据不会包含inprogress日志。这个日志的数据会由active的namenode负责加载(namenode都是以standby启动)。