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 ...
随机推荐
- sublime 常用快捷键备忘
转一篇sublime常用的快捷键备忘 sublime常用快捷键 选择类Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本.Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本 ...
- 使用CXF框架,发布webservice服务,并使用客户端远程访问webservice
使用CXF框架,发布webservice服务,并使用客户端远程访问webservice 1. CXF介绍 :soa的框架 * cxf 是 Celtrix (ESB框架)和 XFire(webs ...
- 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---23
以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下:
- 用Python和Pygame写游戏-从入门到精通(py2exe篇)
这次不是直接讲解下去,而是谈一下如何把我们写的游戏做成一个exe文件,这样一来,用户不需要安装python就可以玩了.扫清了游戏发布一大障碍啊! perl,python,java等编程语言,非常好用, ...
- python标准库:Configparser模块
配置文件test.conf [section1] name = tank age = 28 [section2] ip = 192.168.1.1 port = 8080 示例 # -* - codi ...
- c# 序列化对象为xml 方法
public static string XmlUtils(object obj, bool omitXmlDeclaration = true, bool indent = false, bool ...
- Codeforces Gym100971 L.Chess Match (IX Samara Regional Intercollegiate Programming Contest Russia, Samara, March 13)
这个题就是两个队,看最多能赢的个数,然后比较一下,看两个队是都能赢彼此,还是只有一个队赢的可能性最大.表达能力不好,意思差不多... 和田忌赛马有点像,emnnn,嗯. 代码: 1 #include& ...
- webpack学习(一)安装和命令行、一次js/css的打包体验及不同版本错误
一.前言 找了一个视频教程开始学习webpack,跟着视频学习,在自己的实际操作中发现,出现了很多问题.基本上都是因为版本的原因而导致,自己看的视频是基于webpack 1.x版,而自己现在早已是we ...
- 解决百度ueditor配置上传目录为外部目录时,项目启动访问不到图片的问题。
如图所示,公司项目用到了百度的ueditor,配置的上传目录并不在项目根目录下,而是在外部目录中.于是在上传图片时,出现了无法获取图片的问题. 解决方法:添加该目录至tomcat项目部署目录中,如下图 ...
- ImportError: cannot import name patterns
The use of patterns is deprecated in Django1.10. Therefore do not import 'patterns' and your url pat ...