在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. Bits

    先%SY... 课件链接 求1的个数 以32位整数为例子,最暴力的方法就是一位一位的数,但是这样太不优美... 以下是优美的方法... 这个问题其实就是二进制求和... 我们考虑分治的思想...每一次 ...

  2. IDEA 2017.3 新版本中创建 JSF Web 应用程序缺少 web.xml 的解决办法

    IDEA 2017.3 新版本中默认创建一个 Web 应用程序很可能不会自动创建 web.xml 文件.虽然说从 JavaEE 6.0 开始 Servlet 3.0 规范中就新增了一些注解可以免去传统 ...

  3. EasyUI左边树菜单和datagrid分页

    //这个页面是Home.html 1 <!DOCTYPE html> <html> <head> <meta http-equiv="Content ...

  4. Android大神 博客

    https://github.com/yeungeek/awesome-android-person Android大神 受Trinea的开源项目的启发和参考,也准备列一列Android圈里的大神们. ...

  5. mysql共享锁与排它锁

    共享锁shared lock(也叫读锁read lock)又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁.这保证了其 ...

  6. org.apache.commons.io.Charsets

    requiredCharsets:由Java平台支持字符集对象标准名称,构造一个sorted map. public void test() { Map<String, Charset> ...

  7. ios开发小结之app发布升级

    在近两个月的开发中,遇到了挺多问题的,几天加班加点,最后还是在年前发布并更新了一个版本,欢迎下载无觅下载. 最头疼的问题是提交app审核,之前的工程不太规范,导致一些文件icon没有设置好,直接val ...

  8. 200多种Android动画效果的强悍框架

    admin 发布于2015-10-23 14:33 363/68015 [精品推荐]200多种Android动画效果的强悍框架,太全了,不看这个,再有动画的问题,不理你了^@^ 功能模块和技术方案 只 ...

  9. 更换 mac时 开发证书

    更换 mac时 开发证书,先导出, 后导入,如果出现错误,把projects自动签名改为手动

  10. 常用 linux操作

    查看libreoffice进程 ps -ef | grep libreoffice