在FSNamesystem中,有这么一个成员变量,定义如下:

  1. /**
  2. * Used when this NN is in standby state to read from the shared edit log.
  3. * 当NameNode处于standby状态时用于从共享的edit log读取数据
  4. */
  5. private EditLogTailer editLogTailer = null;

editLogTailer是一个编辑日志edit log的追踪器,它的主要作用就是当NameNode处于standby状态时用于从共享的edit log读取数据。它的构造是在FSNamesystem的startStandbyServices()方法中,代码如下:

  1. editLogTailer = new EditLogTailer(this, conf);
  2. editLogTailer.start();

利用当前FSNamesystem实例this和配置信息conf实例化一个EditLogTailer对象,然后调用其start()方法启动它。

接下来我们看看EditLogTailer的实现,先来看下其成员变量,代码如下:

  1. // 编辑日志跟踪线程EditLogTailerThread实例tailerThread
  2. private final EditLogTailerThread tailerThread;
  3. // HDFS配置信息Configuration实例conf
  4. private final Configuration conf;
  5. // 文件系统命名空间FSNamesystem实例namesystem
  6. private final FSNamesystem namesystem;
  7. // 文件系统编辑日志FSEditLog实例editLog
  8. private FSEditLog editLog;
  9. // Active NameNode地址InetSocketAddress
  10. private InetSocketAddress activeAddr;
  11. // 名字节点通信接口NamenodeProtocol
  12. private NamenodeProtocol cachedActiveProxy = null;
  13. /**
  14. * The last transaction ID at which an edit log roll was initiated.
  15. * 一次编辑日志滚动开始时的最新事务ID
  16. */
  17. private long lastRollTriggerTxId = HdfsConstants.INVALID_TXID;
  18. /**
  19. * The highest transaction ID loaded by the Standby.
  20. * StandBy NameNode加载的最高事务ID
  21. */
  22. private long lastLoadedTxnId = HdfsConstants.INVALID_TXID;
  23. /**
  24. * The last time we successfully loaded a non-zero number of edits from the
  25. * shared directory.
  26. * 最后一次我们从共享目录成功加载一个非零编辑的时间
  27. */
  28. private long lastLoadTimestamp;
  29. /**
  30. * How often the Standby should roll edit logs. Since the Standby only reads
  31. * from finalized log segments, the Standby will only be as up-to-date as how
  32. * often the logs are rolled.
  33. * StandBy NameNode滚动编辑日志的时间间隔。
  34. */
  35. private final long logRollPeriodMs;
  36. /**
  37. * How often the Standby should check if there are new finalized segment(s)
  38. * available to be read from.
  39. * StandBy NameNode检查是否存在可以读取的新的最终日志段的时间间隔
  40. */
  41. private final long sleepTimeMs;

其中,比较重要的几个变量如下:

1、EditLogTailerThread tailerThread:它是编辑日志跟踪线程,

我们再来看下EditLogTailer的构造方法,如下:

  1. public EditLogTailer(FSNamesystem namesystem, Configuration conf) {
  2. // 实例化编辑日志追踪线程EditLogTailerThread
  3. this.tailerThread = new EditLogTailerThread();
  4. // 根据入参初始化配置信息conf和文件系统命名系统namesystem
  5. this.conf = conf;
  6. this.namesystem = namesystem;
  7. // 从namesystem中获取editLog
  8. this.editLog = namesystem.getEditLog();
  9. // 最新加载edit log时间lastLoadTimestamp初始化为当前时间
  10. lastLoadTimestamp = now();
  11. // StandBy NameNode滚动编辑日志的时间间隔logRollPeriodMs
  12. // 取参数dfs.ha.log-roll.period,参数未配置默认为2min
  13. logRollPeriodMs = conf.getInt(DFSConfigKeys.DFS_HA_LOGROLL_PERIOD_KEY,
  14. DFSConfigKeys.DFS_HA_LOGROLL_PERIOD_DEFAULT) * 1000;
  15. // 如果logRollPeriodMs大于等于0
  16. if (logRollPeriodMs >= 0) {
  17. // 调用getActiveNodeAddress()方法初始化Active NameNode地址activeAddr
  18. this.activeAddr = getActiveNodeAddress();
  19. Preconditions.checkArgument(activeAddr.getPort() > 0,
  20. "Active NameNode must have an IPC port configured. " +
  21. "Got address '%s'", activeAddr);
  22. LOG.info("Will roll logs on active node at " + activeAddr + " every " +
  23. (logRollPeriodMs / 1000) + " seconds.");
  24. } else {
  25. LOG.info("Not going to trigger log rolls on active node because " +
  26. DFSConfigKeys.DFS_HA_LOGROLL_PERIOD_KEY + " is negative.");
  27. }
  28. // StandBy NameNode检查是否存在可以读取的新的最终日志段的时间间隔sleepTimeMs
  29. // 取参数dfs.ha.tail-edits.period,参数未配置默认为1min
  30. sleepTimeMs = conf.getInt(DFSConfigKeys.DFS_HA_TAILEDITS_PERIOD_KEY,
  31. DFSConfigKeys.DFS_HA_TAILEDITS_PERIOD_DEFAULT) * 1000;
  32. LOG.debug("logRollPeriodMs=" + logRollPeriodMs +
  33. " sleepTime=" + sleepTimeMs);
  34. }

下面,我们再看下这个十分重要的编辑日志追踪线程EditLogTailerThread的实现,它的构造方法很简单,没有什么可说的,我们着重看下它的run()方法,代码如下:

  1. @Override
  2. public void run() {
  3. SecurityUtil.doAsLoginUserOrFatal(
  4. new PrivilegedAction<Object>() {
  5. @Override
  6. public Object run() {
  7. doWork();
  8. return null;
  9. }
  10. });
  11. }

run()方法内继而调用了doWork()方法,代码如下:

  1. private void doWork() {
  2. // 标志位shouldRun为true时一直循环
  3. while (shouldRun) {
  4. try {
  5. // There's no point in triggering a log roll if the Standby hasn't
  6. // read any more transactions since the last time a roll was
  7. // triggered.
  8. // 自从上次日志滚动触发以来,如果StandBy NameNode没有读到任何事务的话,没有点触发一次日志滚动,
  9. // 如果是自从上次加载后过了太长时间,并且上次编辑日志滚动开始时的最新事务ID小于上次StandBy NameNode加载的最高事务ID
  10. if (tooLongSinceLastLoad() &&
  11. lastRollTriggerTxId < lastLoadedTxnId) {
  12. // 触发Active NameNode进行编辑日志滚动
  13. triggerActiveLogRoll();
  14. }
  15. /**
  16. * Check again in case someone calls {@link EditLogTailer#stop} while
  17. * we're triggering an edit log roll, since ipc.Client catches and
  18. * ignores {@link InterruptedException} in a few places. This fixes
  19. * the bug described in HDFS-2823.
  20. */
  21. // 判断标志位shouldRun,如果其为false的话,退出循环
  22. if (!shouldRun) {
  23. break;
  24. }
  25. // 调用doTailEdits()方法执行日志追踪
  26. doTailEdits();
  27. } catch (EditLogInputException elie) {
  28. LOG.warn("Error while reading edits from disk. Will try again.", elie);
  29. } catch (InterruptedException ie) {
  30. // interrupter should have already set shouldRun to false
  31. continue;
  32. } catch (Throwable t) {
  33. LOG.fatal("Unknown error encountered while tailing edits. " +
  34. "Shutting down standby NN.", t);
  35. terminate(1, t);
  36. }
  37. // 线程休眠sleepTimeMs时间后继续工作
  38. try {
  39. Thread.sleep(sleepTimeMs);
  40. } catch (InterruptedException e) {
  41. LOG.warn("Edit log tailer interrupted", e);
  42. }
  43. }
  44. }

当标志位shouldRun为true时,doWork()方法一直在while循环内执行,其处理逻辑如下:

1、如果是自从上次加载后过了太长时间,并且上次编辑日志滚动开始时的最新事务ID小于上次StandBy NameNode加载的最高事务ID,触发Active NameNode进行编辑日志滚动:

自从上次加载后过了太长时间是根据tooLongSinceLastLoad()方法判断的,而触发Active NameNode进行编辑日志滚动则是通过triggerActiveLogRoll()方法来完成的;

2、判断标志位shouldRun,如果其为false的话,退出循环;

3、调用doTailEdits()方法执行日志追踪;

4、线程休眠sleepTimeMs时间后继续执行上述工作。

我们先来看下如果确定自从上次加载后过了太长时间,tooLongSinceLastLoad()方法代码如下:

  1. /**
  2. * @return true if the configured log roll period has elapsed.
  3. */
  4. private boolean tooLongSinceLastLoad() {
  5. // StandBy NameNode滚动编辑日志的时间间隔logRollPeriodMs大于0,
  6. // 且最后一次我们从共享目录成功加载一个非零编辑的时间到现在的时间间隔大于logRollPeriodMs
  7. return logRollPeriodMs >= 0 &&
  8. (now() - lastLoadTimestamp) > logRollPeriodMs ;
  9. }

它判断的主要依据就是,StandBy NameNode滚动编辑日志的时间间隔logRollPeriodMs大于0,且最后一次我们从共享目录成功加载一个非零编辑的时间到现在的时间间隔大于logRollPeriodMs。

触发Active NameNode进行编辑日志滚动的triggerActiveLogRoll()方法代码如下:

  1. /**
  2. * Trigger the active node to roll its logs.
  3. * 触发Active NameNode滚动日志
  4. */
  5. private void triggerActiveLogRoll() {
  6. LOG.info("Triggering log roll on remote NameNode " + activeAddr);
  7. try {
  8. // 获得Active NameNode的代理,并调用其rollEditLog()方法滚动编辑日志
  9. getActiveNodeProxy().rollEditLog();
  10. // 将上次StandBy NameNode加载的最高事务ID,即lastLoadedTxnId,赋值给上次编辑日志滚动开始时的最新事务ID,即lastRollTriggerTxId,
  11. // 这么做是为了方便进行日志回滚
  12. lastRollTriggerTxId = lastLoadedTxnId;
  13. } catch (IOException ioe) {
  14. LOG.warn("Unable to trigger a roll of the active NN", ioe);
  15. }
  16. }

它首先会获得Active NameNode的代理,并调用其rollEditLog()方法滚动编辑日志,然后将上次StandBy NameNode加载的最高事务ID,即lastLoadedTxnId,赋值给上次编辑日志滚动开始时的最新事务ID,即lastRollTriggerTxId,这么做是为了方便进行日志回滚以及逻辑判断。

好了,最后我们看下最重要的执行日志追踪的doTailEdits()方法吧,代码如下:

  1. @VisibleForTesting
  2. void doTailEdits() throws IOException, InterruptedException {
  3. // Write lock needs to be interruptible here because the
  4. // transitionToActive RPC takes the write lock before calling
  5. // tailer.stop() -- so if we're not interruptible, it will
  6. // deadlock.
  7. // namesystem加写锁
  8. namesystem.writeLockInterruptibly();
  9. try {
  10. // 通过namesystem获取文件系统镜像FSImage实例image
  11. FSImage image = namesystem.getFSImage();
  12. // 通过文件系统镜像FSImage实例image获取最新的事务ID
  13. long lastTxnId = image.getLastAppliedTxId();
  14. if (LOG.isDebugEnabled()) {
  15. LOG.debug("lastTxnId: " + lastTxnId);
  16. }
  17. Collection<EditLogInputStream> streams;
  18. try {
  19. // 从编辑日志editLog中获取编辑日志输入流集合streams,获取的输入流为最新事务ID加1之后的数据
  20. streams = editLog.selectInputStreams(lastTxnId + 1, 0, null, false);
  21. } catch (IOException ioe) {
  22. // This is acceptable. If we try to tail edits in the middle of an edits
  23. // log roll, i.e. the last one has been finalized but the new inprogress
  24. // edits file hasn't been started yet.
  25. LOG.warn("Edits tailer failed to find any streams. Will try again " +
  26. "later.", ioe);
  27. return;
  28. }
  29. if (LOG.isDebugEnabled()) {
  30. LOG.debug("edit streams to load from: " + streams.size());
  31. }
  32. // Once we have streams to load, errors encountered are legitimate cause
  33. // for concern, so we don't catch them here. Simple errors reading from
  34. // disk are ignored.
  35. long editsLoaded = 0;
  36. try {
  37. // 调用文件系统镜像FSImage实例image的loadEdits(),
  38. // 利用编辑日志输入流集合streams,加载编辑日志至目标namesystem中的文件系统镜像FSImage,
  39. // 并获得编辑日志加载的大小editsLoaded
  40. editsLoaded = image.loadEdits(streams, namesystem);
  41. } catch (EditLogInputException elie) {
  42. editsLoaded = elie.getNumEditsLoaded();
  43. throw elie;
  44. } finally {
  45. if (editsLoaded > 0 || LOG.isDebugEnabled()) {
  46. LOG.info(String.format("Loaded %d edits starting from txid %d ",
  47. editsLoaded, lastTxnId));
  48. }
  49. }
  50. if (editsLoaded > 0) {// 如果editsLoaded大于0
  51. // 最后一次我们从共享目录成功加载一个非零编辑的时间lastLoadTimestamp更新为当前时间
  52. lastLoadTimestamp = now();
  53. }
  54. // 上次StandBy NameNode加载的最高事务ID更新为image中最新事务ID
  55. lastLoadedTxnId = image.getLastAppliedTxId();
  56. } finally {
  57. // namesystem去除写锁
  58. namesystem.writeUnlock();
  59. }
  60. }

大体处理流程如下:

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的更多相关文章

  1. HDFS源码分析EditLog之获取编辑日志输入流

    在<HDFS源码分析之EditLogTailer>一文中,我们详细了解了编辑日志跟踪器EditLogTailer的实现,介绍了其内部编辑日志追踪线程EditLogTailerThread的 ...

  2. HDFS源码分析EditLog之读取操作符

    在<HDFS源码分析EditLog之获取编辑日志输入流>一文中,我们详细了解了如何获取编辑日志输入流EditLogInputStream.在我们得到编辑日志输入流后,是不是就该从输入流中获 ...

  3. HDFS源码分析之UnderReplicatedBlocks(一)

    http://blog.csdn.net/lipeng_bigdata/article/details/51160359 UnderReplicatedBlocks是HDFS中关于块复制的一个重要数据 ...

  4. HDFS源码分析数据块校验之DataBlockScanner

    DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...

  5. HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

    HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

  6. HDFS源码分析数据块复制监控线程ReplicationMonitor(一)

    ReplicationMonitor是HDFS中关于数据块复制的监控线程,它的主要作用就是计算DataNode工作,并将复制请求超时的块重新加入到待调度队列.其定义及作为线程核心的run()方法如下: ...

  7. HDFS源码分析之UnderReplicatedBlocks(二)

    UnderReplicatedBlocks还提供了一个数据块迭代器BlockIterator,用于遍历其中的数据块.它是UnderReplicatedBlocks的内部类,有三个成员变量,如下: // ...

  8. HDFS源码分析之LightWeightGSet

    LightWeightGSet是名字节点NameNode在内存中存储全部数据块信息的类BlocksMap需要的一个重要数据结构,它是一个占用较低内存的集合的实现,它使用一个数组array存储元素,使用 ...

  9. HDFS源码分析数据块汇报之损坏数据块检测checkReplicaCorrupt()

    无论是第一次,还是之后的每次数据块汇报,名字名字节点都会对汇报上来的数据块进行检测,看看其是否为损坏的数据块.那么,损坏数据块是如何被检测的呢?本文,我们将研究下损坏数据块检测的checkReplic ...

随机推荐

  1. kernel thread vs user thread

    The most important difference is they use different memory, the kernel mode thread can access any ke ...

  2. HttpHandler,HttpApplication, HttpModule

    选择HttpHandler还是HttpModule? HttpHandler和HttpModule之间有什么差别 之所以有这个疑问,是因为在这二类对象中都可以访问Request, Response对象 ...

  3. 19年的桌面KDE的风雨和陪伴,没有什么能够割舍

    概述 KDE是史上功能最强大的桌面环境之一:开源且可自由使用.19年前,1996年10月14日,德国程序员 Matthias Ettrich 开始了这个美观的桌面环境的开发.KDE 提供了用户界面以及 ...

  4. Selenium2+python自动化(学习笔记2)

    from selenium import webdriverdriver = webdriver.Ie()driver.get=("http://www.baidu.com")dr ...

  5. 转载——分享一个html+js+ashx+easyui+ado.net权限管理系统

    EasyUI.权限管理 这是个都快被搞烂了的组合,但是easyui的确好用,权限管理在项目中的确实用.一直以来博客园里也不少朋友分享过,但是感觉好的要不没源码,要不就是过度设计写的太复杂看不懂,也懒得 ...

  6. Android 项目提交到svn需要忽略的文件和文件夹

  7. httpd安装和配置(cgi、wsgi)

    参考:http://webpy.org/cookbook/mod_wsgi-apache.zh-cn 一.yum方式安装: 1.yum install httpd 输入y后继续. 2.看到一下类似的返 ...

  8. iOS 动画笔记 (一)

    你也肯定喜欢炫酷的动画! 在APP中,动画就是一个点睛之笔!可以给用户增加一些独特的体验感,估计也有许多的和我一样的,看着那些觉得不错的动画,也就只能流口水的孩子,毕竟可能不知道从哪里下手去写!动画学 ...

  9. 立即执行函数(IIFE)

    立即执行函数(IIFE) 看到这里,相信你一定迫不及待地想知道究竟如何做了吧,其实很简单,只需要用括号全部括起来即可,比如下面这样: (function(){ /* code */ }()); 为什么 ...

  10. 2016集训测试赛(十九)Problem A: 24点大师

    Solution 这到题目有意思. 首先题目描述给我们提供了一种非常管用的模型. 按照题目的方法, 我们可以轻松用暴力解决20+的问题; 关键在于如何构造更大的情况: 我们发现 \[ [(n + n) ...