在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. HttpHandler,HttpApplication, HttpModule

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

  2. 【全局变量】mysql查看全局变量以及设置全局变量的值

    1.查看mysql的所有全局变量的值 SHOW GLOBAL VARIABLES 或者 SHOW VARIABLES mysql有很多全局变量,包括系统的一些基本信息,以及mysql的一些基本配置都可 ...

  3. asp.net内置对象 Response对象使用介绍

    Response对象是HttpRespone类的一个实例.该类主要是封装来自ASP.NET操作的HTTP相应信息.Response对象将数据作为请求的结果从服务器发送到客户浏览器中,并提供有关响应的消 ...

  4. Single Number II - LeetCode

    Given an array of integers, every element appears three times except for one. Find that single one. ...

  5. Arc Object开发,概述2

    简介 Esri公司推出的ArcGIS产品是一个非常强大的体系,里面包含ArcGIS Desktop.ArcGIS Server.ArcGIS Engine.ArcSDE.ArcGIS Online等, ...

  6. How To Use Git Source Control with Xcode in iOS 6

    This tutorial is by Malek Trabelsi, a passionate iOS developer from Tunisia focused primarily on mob ...

  7. Android OkHttp经验小结

    OkHttp应该是目前最完善,也是相当流行的一个底层网络请求库.Google都在用,所以有必要深入了解一下,刚好最近在重构公司项目的网络层,就顺便梳理一下.———–12.29————最近暂时没有时间详 ...

  8. 【IntelliJ IDEA】spring boot项目在idea实现自动部署

    转载参考自:https://www.cnblogs.com/winner-0715/p/6666579.html spring-boot-devtools是一个为开发者服务的一个模块,其中最重要的功能 ...

  9. 【hibernate】Hibernate SQL 方言(hibernate.dialect)

    参考如下: RDBMS Dialect DB2 org.hibernate.dialect.DB2Dialect DB2 AS/400 org.hibernate.dialect.DB2400Dial ...

  10. HDU 1041 Computer Transformation 数学DP题解

    本题假设编程是使用DP思想直接打表就能够了. 假设是找规律就须要数学思维了. 规律就是看这些连续的0是从哪里来的. 我找到的规律是:1经过两次裂变之后就会产生一个00: 00经过两次裂变之后也会产生新 ...