HDFS源码分析之EditLogTailer
在FSNamesystem中,有这么一个成员变量,定义如下:
- /**
- * Used when this NN is in standby state to read from the shared edit log.
- * 当NameNode处于standby状态时用于从共享的edit log读取数据
- */
- private EditLogTailer editLogTailer = null;
editLogTailer是一个编辑日志edit log的追踪器,它的主要作用就是当NameNode处于standby状态时用于从共享的edit log读取数据。它的构造是在FSNamesystem的startStandbyServices()方法中,代码如下:
- editLogTailer = new EditLogTailer(this, conf);
- editLogTailer.start();
利用当前FSNamesystem实例this和配置信息conf实例化一个EditLogTailer对象,然后调用其start()方法启动它。
接下来我们看看EditLogTailer的实现,先来看下其成员变量,代码如下:
- // 编辑日志跟踪线程EditLogTailerThread实例tailerThread
- private final EditLogTailerThread tailerThread;
- // HDFS配置信息Configuration实例conf
- private final Configuration conf;
- // 文件系统命名空间FSNamesystem实例namesystem
- private final FSNamesystem namesystem;
- // 文件系统编辑日志FSEditLog实例editLog
- private FSEditLog editLog;
- // Active NameNode地址InetSocketAddress
- private InetSocketAddress activeAddr;
- // 名字节点通信接口NamenodeProtocol
- private NamenodeProtocol cachedActiveProxy = null;
- /**
- * The last transaction ID at which an edit log roll was initiated.
- * 一次编辑日志滚动开始时的最新事务ID
- */
- private long lastRollTriggerTxId = HdfsConstants.INVALID_TXID;
- /**
- * The highest transaction ID loaded by the Standby.
- * StandBy NameNode加载的最高事务ID
- */
- private long lastLoadedTxnId = HdfsConstants.INVALID_TXID;
- /**
- * The last time we successfully loaded a non-zero number of edits from the
- * shared directory.
- * 最后一次我们从共享目录成功加载一个非零编辑的时间
- */
- private long lastLoadTimestamp;
- /**
- * How often the Standby should roll edit logs. Since the Standby only reads
- * from finalized log segments, the Standby will only be as up-to-date as how
- * often the logs are rolled.
- * StandBy NameNode滚动编辑日志的时间间隔。
- */
- private final long logRollPeriodMs;
- /**
- * How often the Standby should check if there are new finalized segment(s)
- * available to be read from.
- * StandBy NameNode检查是否存在可以读取的新的最终日志段的时间间隔
- */
- private final long sleepTimeMs;
其中,比较重要的几个变量如下:
1、EditLogTailerThread tailerThread:它是编辑日志跟踪线程,
我们再来看下EditLogTailer的构造方法,如下:
- public EditLogTailer(FSNamesystem namesystem, Configuration conf) {
- // 实例化编辑日志追踪线程EditLogTailerThread
- this.tailerThread = new EditLogTailerThread();
- // 根据入参初始化配置信息conf和文件系统命名系统namesystem
- this.conf = conf;
- this.namesystem = namesystem;
- // 从namesystem中获取editLog
- this.editLog = namesystem.getEditLog();
- // 最新加载edit log时间lastLoadTimestamp初始化为当前时间
- lastLoadTimestamp = now();
- // StandBy NameNode滚动编辑日志的时间间隔logRollPeriodMs
- // 取参数dfs.ha.log-roll.period,参数未配置默认为2min
- logRollPeriodMs = conf.getInt(DFSConfigKeys.DFS_HA_LOGROLL_PERIOD_KEY,
- DFSConfigKeys.DFS_HA_LOGROLL_PERIOD_DEFAULT) * 1000;
- // 如果logRollPeriodMs大于等于0
- if (logRollPeriodMs >= 0) {
- // 调用getActiveNodeAddress()方法初始化Active NameNode地址activeAddr
- this.activeAddr = getActiveNodeAddress();
- Preconditions.checkArgument(activeAddr.getPort() > 0,
- "Active NameNode must have an IPC port configured. " +
- "Got address '%s'", activeAddr);
- LOG.info("Will roll logs on active node at " + activeAddr + " every " +
- (logRollPeriodMs / 1000) + " seconds.");
- } else {
- LOG.info("Not going to trigger log rolls on active node because " +
- DFSConfigKeys.DFS_HA_LOGROLL_PERIOD_KEY + " is negative.");
- }
- // StandBy NameNode检查是否存在可以读取的新的最终日志段的时间间隔sleepTimeMs
- // 取参数dfs.ha.tail-edits.period,参数未配置默认为1min
- sleepTimeMs = conf.getInt(DFSConfigKeys.DFS_HA_TAILEDITS_PERIOD_KEY,
- DFSConfigKeys.DFS_HA_TAILEDITS_PERIOD_DEFAULT) * 1000;
- LOG.debug("logRollPeriodMs=" + logRollPeriodMs +
- " sleepTime=" + sleepTimeMs);
- }
下面,我们再看下这个十分重要的编辑日志追踪线程EditLogTailerThread的实现,它的构造方法很简单,没有什么可说的,我们着重看下它的run()方法,代码如下:
- @Override
- public void run() {
- SecurityUtil.doAsLoginUserOrFatal(
- new PrivilegedAction<Object>() {
- @Override
- public Object run() {
- doWork();
- return null;
- }
- });
- }
run()方法内继而调用了doWork()方法,代码如下:
- private void doWork() {
- // 标志位shouldRun为true时一直循环
- while (shouldRun) {
- try {
- // There's no point in triggering a log roll if the Standby hasn't
- // read any more transactions since the last time a roll was
- // triggered.
- // 自从上次日志滚动触发以来,如果StandBy NameNode没有读到任何事务的话,没有点触发一次日志滚动,
- // 如果是自从上次加载后过了太长时间,并且上次编辑日志滚动开始时的最新事务ID小于上次StandBy NameNode加载的最高事务ID
- if (tooLongSinceLastLoad() &&
- lastRollTriggerTxId < lastLoadedTxnId) {
- // 触发Active NameNode进行编辑日志滚动
- triggerActiveLogRoll();
- }
- /**
- * Check again in case someone calls {@link EditLogTailer#stop} while
- * we're triggering an edit log roll, since ipc.Client catches and
- * ignores {@link InterruptedException} in a few places. This fixes
- * the bug described in HDFS-2823.
- */
- // 判断标志位shouldRun,如果其为false的话,退出循环
- if (!shouldRun) {
- break;
- }
- // 调用doTailEdits()方法执行日志追踪
- doTailEdits();
- } catch (EditLogInputException elie) {
- LOG.warn("Error while reading edits from disk. Will try again.", elie);
- } catch (InterruptedException ie) {
- // interrupter should have already set shouldRun to false
- continue;
- } catch (Throwable t) {
- LOG.fatal("Unknown error encountered while tailing edits. " +
- "Shutting down standby NN.", t);
- terminate(1, t);
- }
- // 线程休眠sleepTimeMs时间后继续工作
- try {
- Thread.sleep(sleepTimeMs);
- } catch (InterruptedException e) {
- LOG.warn("Edit log tailer interrupted", e);
- }
- }
- }
当标志位shouldRun为true时,doWork()方法一直在while循环内执行,其处理逻辑如下:
1、如果是自从上次加载后过了太长时间,并且上次编辑日志滚动开始时的最新事务ID小于上次StandBy NameNode加载的最高事务ID,触发Active NameNode进行编辑日志滚动:
自从上次加载后过了太长时间是根据tooLongSinceLastLoad()方法判断的,而触发Active NameNode进行编辑日志滚动则是通过triggerActiveLogRoll()方法来完成的;
2、判断标志位shouldRun,如果其为false的话,退出循环;
3、调用doTailEdits()方法执行日志追踪;
4、线程休眠sleepTimeMs时间后继续执行上述工作。
我们先来看下如果确定自从上次加载后过了太长时间,tooLongSinceLastLoad()方法代码如下:
- /**
- * @return true if the configured log roll period has elapsed.
- */
- private boolean tooLongSinceLastLoad() {
- // StandBy NameNode滚动编辑日志的时间间隔logRollPeriodMs大于0,
- // 且最后一次我们从共享目录成功加载一个非零编辑的时间到现在的时间间隔大于logRollPeriodMs
- return logRollPeriodMs >= 0 &&
- (now() - lastLoadTimestamp) > logRollPeriodMs ;
- }
它判断的主要依据就是,StandBy NameNode滚动编辑日志的时间间隔logRollPeriodMs大于0,且最后一次我们从共享目录成功加载一个非零编辑的时间到现在的时间间隔大于logRollPeriodMs。
触发Active NameNode进行编辑日志滚动的triggerActiveLogRoll()方法代码如下:
- /**
- * Trigger the active node to roll its logs.
- * 触发Active NameNode滚动日志
- */
- private void triggerActiveLogRoll() {
- LOG.info("Triggering log roll on remote NameNode " + activeAddr);
- try {
- // 获得Active NameNode的代理,并调用其rollEditLog()方法滚动编辑日志
- getActiveNodeProxy().rollEditLog();
- // 将上次StandBy NameNode加载的最高事务ID,即lastLoadedTxnId,赋值给上次编辑日志滚动开始时的最新事务ID,即lastRollTriggerTxId,
- // 这么做是为了方便进行日志回滚
- lastRollTriggerTxId = lastLoadedTxnId;
- } catch (IOException ioe) {
- LOG.warn("Unable to trigger a roll of the active NN", ioe);
- }
- }
它首先会获得Active NameNode的代理,并调用其rollEditLog()方法滚动编辑日志,然后将上次StandBy NameNode加载的最高事务ID,即lastLoadedTxnId,赋值给上次编辑日志滚动开始时的最新事务ID,即lastRollTriggerTxId,这么做是为了方便进行日志回滚以及逻辑判断。
好了,最后我们看下最重要的执行日志追踪的doTailEdits()方法吧,代码如下:
- @VisibleForTesting
- void doTailEdits() throws IOException, InterruptedException {
- // Write lock needs to be interruptible here because the
- // transitionToActive RPC takes the write lock before calling
- // tailer.stop() -- so if we're not interruptible, it will
- // deadlock.
- // namesystem加写锁
- namesystem.writeLockInterruptibly();
- try {
- // 通过namesystem获取文件系统镜像FSImage实例image
- FSImage image = namesystem.getFSImage();
- // 通过文件系统镜像FSImage实例image获取最新的事务ID
- long lastTxnId = image.getLastAppliedTxId();
- if (LOG.isDebugEnabled()) {
- LOG.debug("lastTxnId: " + lastTxnId);
- }
- Collection<EditLogInputStream> streams;
- try {
- // 从编辑日志editLog中获取编辑日志输入流集合streams,获取的输入流为最新事务ID加1之后的数据
- streams = editLog.selectInputStreams(lastTxnId + 1, 0, null, false);
- } catch (IOException ioe) {
- // This is acceptable. If we try to tail edits in the middle of an edits
- // log roll, i.e. the last one has been finalized but the new inprogress
- // edits file hasn't been started yet.
- LOG.warn("Edits tailer failed to find any streams. Will try again " +
- "later.", ioe);
- return;
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("edit streams to load from: " + streams.size());
- }
- // Once we have streams to load, errors encountered are legitimate cause
- // for concern, so we don't catch them here. Simple errors reading from
- // disk are ignored.
- long editsLoaded = 0;
- try {
- // 调用文件系统镜像FSImage实例image的loadEdits(),
- // 利用编辑日志输入流集合streams,加载编辑日志至目标namesystem中的文件系统镜像FSImage,
- // 并获得编辑日志加载的大小editsLoaded
- editsLoaded = image.loadEdits(streams, namesystem);
- } catch (EditLogInputException elie) {
- editsLoaded = elie.getNumEditsLoaded();
- throw elie;
- } finally {
- if (editsLoaded > 0 || LOG.isDebugEnabled()) {
- LOG.info(String.format("Loaded %d edits starting from txid %d ",
- editsLoaded, lastTxnId));
- }
- }
- if (editsLoaded > 0) {// 如果editsLoaded大于0
- // 最后一次我们从共享目录成功加载一个非零编辑的时间lastLoadTimestamp更新为当前时间
- lastLoadTimestamp = now();
- }
- // 上次StandBy NameNode加载的最高事务ID更新为image中最新事务ID
- lastLoadedTxnId = image.getLastAppliedTxId();
- } finally {
- // namesystem去除写锁
- namesystem.writeUnlock();
- }
- }
大体处理流程如下:
1、首先,namesystem加写锁;
2、通过namesystem获取文件系统镜像FSImage实例image;
3、通过文件系统镜像FSImage实例image获取最新的事务ID,即lastTxnId;
4、从编辑日志editLog中获取编辑日志输入流集合streams,获取的输入流为最新事务ID加1之后的数据:
ps:注意,这个编辑日志输入流集合streams并非读取的是editLog对象中的数据,毕竟editLog也是根据namesystem来获取的,如果从其中读取数据再加载到namesystem中的fsimage中,没有多大意义,这个日志输入流实际上是通过Hadoop HA中的JournalNode来获取的,这个我们以后再分析。
5、调用文件系统镜像FSImage实例image的loadEdits(),利用编辑日志输入流集合streams,加载编辑日志至目标namesystem中的文件系统镜像FSImage,并获得编辑日志加载的大小editsLoaded;
6、如果editsLoaded大于0,最后一次我们从共享目录成功加载一个非零编辑的时间lastLoadTimestamp更新为当前时间;
7、上次StandBy NameNode加载的最高事务ID更新为image中最新事务ID;
8、namesystem去除写锁。
部分涉及FSImage、FSEditLog、JournalNode等的细节,限于篇幅,我们以后再分析!
HDFS源码分析之EditLogTailer的更多相关文章
- HDFS源码分析EditLog之获取编辑日志输入流
在<HDFS源码分析之EditLogTailer>一文中,我们详细了解了编辑日志跟踪器EditLogTailer的实现,介绍了其内部编辑日志追踪线程EditLogTailerThread的 ...
- HDFS源码分析EditLog之读取操作符
在<HDFS源码分析EditLog之获取编辑日志输入流>一文中,我们详细了解了如何获取编辑日志输入流EditLogInputStream.在我们得到编辑日志输入流后,是不是就该从输入流中获 ...
- HDFS源码分析之UnderReplicatedBlocks(一)
http://blog.csdn.net/lipeng_bigdata/article/details/51160359 UnderReplicatedBlocks是HDFS中关于块复制的一个重要数据 ...
- HDFS源码分析数据块校验之DataBlockScanner
DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...
- HDFS源码分析数据块复制监控线程ReplicationMonitor(二)
HDFS源码分析数据块复制监控线程ReplicationMonitor(二)
- HDFS源码分析数据块复制监控线程ReplicationMonitor(一)
ReplicationMonitor是HDFS中关于数据块复制的监控线程,它的主要作用就是计算DataNode工作,并将复制请求超时的块重新加入到待调度队列.其定义及作为线程核心的run()方法如下: ...
- HDFS源码分析之UnderReplicatedBlocks(二)
UnderReplicatedBlocks还提供了一个数据块迭代器BlockIterator,用于遍历其中的数据块.它是UnderReplicatedBlocks的内部类,有三个成员变量,如下: // ...
- HDFS源码分析之LightWeightGSet
LightWeightGSet是名字节点NameNode在内存中存储全部数据块信息的类BlocksMap需要的一个重要数据结构,它是一个占用较低内存的集合的实现,它使用一个数组array存储元素,使用 ...
- HDFS源码分析数据块汇报之损坏数据块检测checkReplicaCorrupt()
无论是第一次,还是之后的每次数据块汇报,名字名字节点都会对汇报上来的数据块进行检测,看看其是否为损坏的数据块.那么,损坏数据块是如何被检测的呢?本文,我们将研究下损坏数据块检测的checkReplic ...
随机推荐
- set基本用法---1
#include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #inclu ...
- ckeditor与ckfinder的使用方法 .NET (转载)
原文发布时间为:2009-11-25 -- 来源于本人的百度文章 [由搬家工具导入] ckeditor与ckfinder的使用方法 .NET (转载) ckeditor 3.0.1学习笔记 一.ck ...
- Mac air苹果笔记本安装Win10双系统教程(绝对能成功,超详细!)[转]
转自:http://www.xitonghe.com/jiaocheng/anzhuang-4676.html 在MAc苹果电脑,Mac air上安装Windows7相信大家都已经会了吧,好吧Win7 ...
- Linux内核情景分析之消息队列
早期的Unix通信只有管道与信号,管道的缺点: 所载送的信息是无格式的字节流,不知道分界线在哪,也没通信规范,另外缺乏控制手段,比如保温优先级,管道机制的大小只有1页,管道很容易写满而读取没有及时,发 ...
- 谈谈JS中的原型
不知道大家对JS中的原型理解的怎么样,我想如果大家对JS中的原型对象以及prototype属性十分熟悉的话对后面原型链以及继承的理解会十分的容易,这里想和大家分享自己对其的理解,请先看下面这段代码O( ...
- ext2/3/4的inode结构说明
系统环境:Ubuntu15.10/ext4 今天在复习<鸟哥的私房菜-基础学习篇>,看到inode大小为128bytes,想看下这128字节里面到底是什么样的. 于是我查了下google, ...
- C结构体struct用法小结
结构体和int,float等类型一样是一种常用的类型,它是由各种基本数据类型构成,通常包含有struct关键字,结构体名,结构体成员,结构体变量. 一.结构体定义 通常有3种定义方式,以例子方式表示: ...
- linux 常用命令: basename 去掉路径和扩展名
basename: 去掉路径和扩展名 /bin/basename coreutils-8.4-9.el6.x86_64 basename命令用于去掉路径信息,返回纯粹的文件名,如果指定的文件的扩展 ...
- Codeforces 782B The Meeting Place Cannot Be Changed(二分答案)
题目链接 The Meeting Place Cannot Be Changed 二分答案即可. check的时候先算出每个点可到达的范围的区间,然后求并集.判断一下是否满足l <= r就好了. ...
- OceanBase 2.1 的ORACLE兼容性能力探秘
概述 OceanBase是一款通用的分布式关系型数据库,目前内部业务使用比较多有两个版本:1.4和2.1.OceanBase每个版本变化总能带给人很多惊喜,其中2.1版本实现了ORACLE很多特性的兼 ...