HDFS的StartupProgress启动跟踪分析

前言


想必HDFS集群的起停操作对于HDFS的使用者来说绝对不是一件陌生的事情.一般情况下我们重启集群服务是出于这2点原因:1).集群新增配置项,需要重启集群服务才能生效.2).对集群相关jar包程序进行了更新,需要重启服务来运行最新的jar包.于是我们重启了集群,一般的我们看它是否启动成功,一定会关注它的输出日志.比如说在HDFS中,我们会比较关注NameNode的启动日志.当然这的确是一个有效的办法,但是我们是否有其他更方便快捷的方法呢?比如说我不想登到NameNode所在节点上去看日志.最后的答案是有的.至于这里面到底是什么一个过程和原理呢?这就是本文将要重点阐述的主题:HDFS的StartupProgress.

HDFS的StartupProgress


这里可以把StartupProgress理解为Startup,Startup就是启动的意思.HDFS的Startup启动过程主要指的是NameNode的启动.在hdfs server端的project下有专门的package目录来存放这部分的代码.正如StartupProgress名称所展示的那样,此模块的功能并不是一个HDFS启动控制的角色,而是说是一种启动跟踪,并进行进度汇报的一个角色.因为HDFS的StartupProgress包下的代码并不多,我们可以做一个详细的分析.相关类文件如下图所示:

这里写图片描述

在上图中,我总共将此包下的相关类分成了3大类:

第一大类,StartupProgress启动进度信息核心类:此大类中包括了以StartupProgress为核心的类以及相关周边类, StartupProgressView和StartupProgressMetrics.前者是StartupProgress类的一个”瞬时数据拷贝”,后者是StartupProgress的统计计数类.
第二大类,Phase阶段类:在NameNode的启动过程中,会经历很多个阶段,而每个阶段的执行情况就是由这里的Phase类所控制.
第三大类,Set阶段步骤类:Set步骤相比于Phase阶段而言,是更细粒度一级的单位,用一句话来概况就是每个Phase阶段内包含了一个或多个Step.

背景知识: NameNode的启动过程


在学习了解HDFS StartupProgress内部代码之前,我们有必要事先了解NameNode的启动过程.下面是NameNode启动过程的简要描述:

1.NameNode启动开始,首先加载fsImage镜像文件,里面将会花比较多的时间解析这个文件.
2.FsImage镜像文件加载完毕之后,apply应用editlog日志文件到NameNode的内存中,如果editlog非常多,也会消耗一定的时间.
3.EditLog应用完毕,NameNode会做一次checkpoint操作.此时会生成一个新的fsImage镜像文件,也就是说,此时的fsImage文件已经是最新的元数据了.
4.以上文件数据全部加载完毕之后,NameNode将会进入SafeMode安全模式,等待DataNode块的上报.上报块数达到安全模式所规定的阈值要求,安全模式自然就会退出,随后NameNode正式启动完毕.

StartupProgress内的阶段和步骤


上节NameNode的4个启动过程在StartupProgress的Phase类都有相对应的声明,代码如下:

public enum Phase {
  /**
   * The namenode is loading the fsimage file into memory.
   */
  LOADING_FSIMAGE("LoadingFsImage", "Loading fsimage"),

  /**
   * The namenode is loading the edits file and applying its operations to the
   * in-memory metadata.
   */
  LOADING_EDITS("LoadingEdits", "Loading edits"),

  /**
   * The namenode is saving a new checkpoint.
   */
  SAVING_CHECKPOINT("SavingCheckpoint", "Saving checkpoint"),

  /**
   * The namenode has entered safemode, awaiting block reports from data nodes.
   */
  SAFEMODE("SafeMode", "Safe mode");
  ...

在每个阶段内,由更细粒度的Step构成.StartupProgress包下的StepType类中总共定义了如下几类的Step:

public enum StepType {
  /**
   * The namenode has entered safemode and is awaiting block reports from
   * datanodes.
   */
  AWAITING_REPORTED_BLOCKS("AwaitingReportedBlocks", "awaiting reported blocks"),

  /**
   * The namenode is performing an operation related to delegation keys.
   */
  DELEGATION_KEYS("DelegationKeys", "delegation keys"),

  /**
   * The namenode is performing an operation related to delegation tokens.
   */
  DELEGATION_TOKENS("DelegationTokens", "delegation tokens"),

  /**
   * The namenode is performing an operation related to inodes.
   */
  INODES("Inodes", "inodes"),

  /**
   * The namenode is performing an operation related to cache pools.
   */
  CACHE_POOLS("CachePools", "cache pools"),

  /**
   * The namenode is performing an operation related to cache entries.
   */
  CACHE_ENTRIES("CacheEntries", "cache entries");
  ...

Phase与Set之间是包含与被包含的关系.每个Phase与Set在执行的过程中总共会经历以下3个状态的变化:

  • 1.PENDING
  • 2.RUNNING
  • 3.COMPLETE

这3个状态的归属将由Phase或Step的开始时间和结束时间所决定.Phase与Set的相关结构图如下:

这里写图片描述

在NameNode的启动过程中,会经历以上Phase和Step的执行,它们各自的执行状况由对应的跟踪类负责记录.Phase对应的是PhaseTracking,Step对应的是StepTracking.它们都继承于AbstractTracking.最底层的跟踪类包含了2个最基本的变量信息:开始时间和结束时间.如下:

abstract class AbstractTracking implements Cloneable {
  long beginTime = Long.MIN_VALUE;
  long endTime = Long.MIN_VALUE;

  ...
}

在2类跟踪器内部是如何跟踪此过程的进度状况呢?首先我们来看PhaseTracking类:

final class PhaseTracking extends AbstractTracking {
  String file;
  long size = Long.MIN_VALUE;
  // Step步骤集合以及对应的跟踪器类
  final ConcurrentMap<Step, StepTracking> steps =
    new ConcurrentHashMap<Step, StepTracking>();
  ...

从上面的代码可以看出,Phase与Step的确是包含与被包含的关系,Phase的跟踪监控需要依赖于每个子Step的跟踪监控.

下面是StepTracking的跟踪类:

final class StepTracking extends AbstractTracking {
  // 计数值
  AtomicLong count = new AtomicLong();
  // 总数值
  long total = Long.MIN_VALUE;
  ...

在此跟踪类中,有2个指标变量:计数值和总数值.而且计数值变量count特意设计为AtomicLong类型,这表明了此过程的计数更新过程将会存在并发竞争的问题,使用AtomicLong能保证原子的更新.

StartupProgress的原理与调用


上节详细阐述了Phase和Step的结构构造之后,下面我们要看看在NameNode的Startup启动过程中到底是如何调用这些对象的.这部分的逻辑在核心类StartupProgress中.我们找到这个类的声明:

public class StartupProgress {
  // 包含了各个Phase阶段
  // package-private for access by StartupProgressView
  final Map<Phase, PhaseTracking> phases =
    new ConcurrentHashMap<Phase, PhaseTracking>();
  ...

我们马上可以看到StartupProgress类中包含了各个阶段的集合.我们可以猜想这个phases将会在后面NameNode的启动过程中陆续加入各个Phase.那么StartupProgress是在何时被创建的呢?答案在下面所示的代码中,总共分为3个相关的调用:

  // 1.初始化StartupProgress实例子
  private static final StartupProgress startupProgress = new StartupProgress();
  protected void initialize(Configuration conf) throws IOException {
    ...
    // 2.注册StartupProgress Metric统计
    StartupProgressMetrics.register(startupProgress);
  private void startHttpServer(final Configuration conf) throws IOException {
    httpServer = new NameNodeHttpServer(conf, this, getHttpServerBindAddress(conf));
    httpServer.start();
    // 3.赋值startupProgress到NameNodeHttpServer中,这个将会与StartupProgressView相关
    httpServer.setStartupProgress(startupProgress);
  }

在初始化实例的时候,将会加入所有的Phase对象,也就是之前提到的4个阶段.

  public StartupProgress() {
    for (Phase phase: EnumSet.allOf(Phase.class)) {
      phases.put(phase, new PhaseTracking());
    }
  }

那么现在问题来了,StartupProgress对象是如何标记Phase,Set运行的开始结束?我们以LOAD_FSIMAGE阶段为例子.

  private boolean loadFSImage(FSNamesystem target, StartupOption startOpt,
      MetaRecoveryContext recovery)
      throws IOException {
    ...
    // NameNode中获取StartupProgress实例
    StartupProgress prog = NameNode.getStartupProgress();
    // 开始LOADING_FSIMAGE阶段
    prog.beginPhase(Phase.LOADING_FSIMAGE);
    File phaseFile = imageFiles.get(0).getFile();
    // 设置此阶段用到的文件路径以及大小
    prog.setFile(Phase.LOADING_FSIMAGE, phaseFile.getAbsolutePath());
    prog.setSize(Phase.LOADING_FSIMAGE, phaseFile.length());
    boolean needToSave = inspector.needToSave();
    ...

继续往里看beginPhase方法

  public void beginPhase(Phase phase) {
    // 判断启动过程是否结束
    if (!isComplete()) {
      // 如果没有结束,设置此阶段的开始时间为当前时间
      phases.get(phase).beginTime = monotonicNow();
    }
  }

阶段开始执行了之后,将会经历内部Step的执行,比如在load fsimage开始之后,途中会经历inodes的加载,代码如下:

    public void load(File curFile) throws IOException {
      checkNotLoaded();
      assert curFile != null : "curFile is null";
      // 获取StartupProgress对象实例
      StartupProgress prog = NameNode.getStartupProgress();
      // 新建INODES的Step对象
      Step step = new Step(StepType.INODES);
      // 开始此Step
      prog.beginStep(Phase.LOADING_FSIMAGE, step);
      ...

我们继续进入beginStep方法,

  public void beginStep(Phase phase, Step step) {
    // 同样判断启动过程是否已完成
    if (!isComplete()) {
      // 设置开始时间
      lazyInitStep(phase, step).beginTime = monotonicNow();
    }
  }

这部分代码与之前的beginStep方法极为相似,但是这里调用的是lazyInitStep方法,我们进入此方法来近一步查看其中的逻辑:

  private StepTracking lazyInitStep(Phase phase, Step step) {
    // 获取此阶段的Step集合
    ConcurrentMap<Step, StepTracking> steps = phases.get(phase).steps;
    // 如果不存在目标step,则加入
    if (!steps.containsKey(step)) {
      steps.putIfAbsent(step, new StepTracking());
    }
    return steps.get(step);
  }

在Phase和Step结束的时候,对应的会执行endPhase/endStep方法,同时会设置相关计数值.

prog.endPhase(Phase.LOADING_FSIMAGE);
    ...
        // Step结束之后,结束此step
        prog.endStep(Phase.LOADING_FSIMAGE, step);
        // Now that the step is finished, set counter equal to total to adjust
        // for possible under-counting due to reference inodes.
        // 同时设置Counter
        prog.setCount(Phase.LOADING_FSIMAGE, step, numFiles);
    ...

其他的Phase与Step的执行过程完全类似,这里就不一一展开描述了.但是有一个Step比较特殊,我要额外提一下,就是LOADING_EDITS阶段的Step.这阶段的每个step做的事情是apply editlog文件数据到内存中.但是这类Step类型并没有事先存在,而是直接根据editlog文件名new出来的,代码如下:

  long loadFSEdits(EditLogInputStream edits, long expectedStartingTxId,
      StartupOption startOpt, MetaRecoveryContext recovery) throws IOException {
    StartupProgress prog = NameNode.getStartupProgress();
    // 根据edits文件输入流创建新的step
    Step step = createStartupProgressStep(edits);
    // 开始此step
    prog.beginStep(Phase.LOADING_EDITS, step);
    ...

Startup过程内的Phase与Set的对应关系如下图所示:

这里写图片描述

StartupProgress的”瞬时映像”: StartupProgressView


上面花了一定的篇幅介绍了StartupProgress内部的进度追踪原理以及过程,那么有什么办法可以使用户获取这些数据信息呢?HDFS的设计者早已经帮我们考虑到这样的需求点了.答案是通过StartupProgressView.正如小标题所描述的那样,StartupProgressView是StartupProgress类的一个”瞬时数据拷贝”.这个时间点是这个状态数据,5分钟后又会变成另外的状态数据.在这里我们主要关注以下2个方面:

  • StartupProgressView如何对StartupProgress启动过程的数据做拷贝同步.
  • StartupProgressView如何将自身的数据展示给外部用户.

首先第一个方面,StartupProgressView在对外展示进度数据之前,需要对当前StartupProgress的进度数据做拷贝.相关代码如下:

  StartupProgressView(StartupProgress prog) {
    phases = new HashMap<Phase, PhaseTracking>();
    for (Map.Entry<Phase, PhaseTracking> entry: prog.phases.entrySet()) {
      // 对StartupProgress中的每个Phase的跟踪数据做拷贝
      phases.put(entry.getKey(), entry.getValue().clone());
    }
  }

在每个PhaseTracking的拷贝过程中,会近一步进行StepTracking的数据拷贝.拷贝完毕之后,用户如何能够访问到其数据呢?HDFS内部为我们提供了对此相应的http请求服务,换句话说,我们可以通过http请求的方式获取StartupProgressView的数据,从而我们也可以通过web界面直接查看HDFS启动进度数据.Http请求服务处理代码如下:

public class StartupProgressServlet extends DfsServlet {
  ...
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setContentType("application/json; charset=UTF-8");
    // 获取StartupProgress实例
    StartupProgress prog = NameNodeHttpServer.getStartupProgressFromContext(
      getServletContext());
    // 获取此刻的一份数据拷贝
    StartupProgressView view = prog.createView();
    // 将拷贝数据以json格式返回
    JsonGenerator json = new JsonFactory().createJsonGenerator(resp.getWriter());
    try {
      json.writeStartObject();
      json.writeNumberField(ELAPSED_TIME, view.getElapsedTime());
      json.writeNumberField(PERCENT_COMPLETE, view.getPercentComplete());
      json.writeArrayFieldStart(PHASES);
      ...
      }

      json.writeEndArray();
      json.writeEndObject();
    } finally {
      IOUtils.cleanup(LOG, json);
    }
  }

下面是通过NameNode的Web界面查看的StartupProgress启动进度信息图,可以与之前提到的Phase和Step相对照,完全是吻合的.

这里写图片描述
这里写图片描述

以上就是本文所要阐述的关于HDFS Startup方面的相关内容,希望能给大家带来收获.

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
1、前言 进入 云计算 的时代,各大云提供商AWS, 阿里云 纷纷推出针对 Docker 的服务,现在Docker是十分火爆,那么Docker到底是什麽,让我们来体验一下。 2、Docker是什麽 Docker是一个开源的应用容器引擎,可以把应用以及依赖包放到一个可移植的容器中,然后发布到任何流行的  Linux  系统上,通过这种方式实现虚拟化。 提到虚拟化,大家应该十分熟悉了,有 VMware ,Xen, KVM 等等很多。那么,Docker和VM有什么不同呢,我们用官网的一张图来说明一下。 可以看出

MAT使用的几张图例技巧 - 2016-07-23 14:07:57

1 下面三个是内存泄漏可能性比较大的地方  problem suspect 1 problem suspect 2 点击detail 可以看详细  如果发现里面有自己工程包里面的重复的大量对象的创建 2 在dominator_tree 可以对象按照 group by package 分类 便于查看那部分代码出问题 如果自己的工程包下面占用了大量内存 可能就是问题所在 问题一般可能出在 占用最多的地方 3  选中一个节点 右键查看with incoming reference 可以看  ps :( List
重点发掘自己每天是否有学到或者感受到新东西,一旦可以感知这些细节,那么心态就会平和、也更容易坚持下来,自然而然就可以等到突破瓶颈的时候。 时间是一个很公平的东西,去体验不同的岗位去寻找自己真正想做的,是一个不错的思路。最怕的是目的性不明确的变化。 事实上那怕我们专注于一个领域,在某些时候还要跳出这个领域,开眼界后再回归方能更上一层楼。 ——祝晓春 手工测试二三事   @ 安琪儿的梦 o_0   提问:一直做手工测试会不会没有前 途 ? 任何一个工作和岗位都会有前途的,但是并不是每个人都会达到很大的高度。关

Spring+SpringMVC+Mybatis整合笔记 - 2016-07-22 22:07:05

出于兴趣,最近在研究SSM,参考着别人的技术资料并结合实际情况,一步一步的跑通了SSM整合的整个过程,现整理如下,希望对于同样对SSM有兴趣的你有所帮助,当然,因本人能力有限,可能有些问题,希望您能够指出来,咱们一起学习进步。   主要技术点: l  Spring + SpringMVC + Mybatis的整合 l  使用maven管理jar包及版本 l  使用mybatis generator代码生成器自动生成代码 l  使用SpringJUnit4ClassRunner进行单元测试   主要配置环境

DOS命令大全(经典收藏) - 2016-07-22 22:07:05

#1 一: net use \\ip\ipc$ " " /user:" " 建立IPC空链接 net use \\ip\ipc$ "密码" /user:"用户名" 建立IPC非空链接 net use h: \\ip\c$ "密码" /user:"用户名" 直接登陆后映射对方C:到本地为H: net use h: \\ip\c$ 登陆后映射对方C:到本地为H: net use \\ip\ipc$ /del 删除IPC链接 net use h: /del 删除映射对方到本地的为H:的映射 net user 用
原文地址:【 http://www.sciencedirect.com/science/article/pii/S0167865514001056 】 一、传统的Mean-Shift 原文地址:【 http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=1195991tag=1 】 个人理解(可能存在偏差,会不断改进): Mean-Shift算法用于tracking时,通过最小化两个概率密度函数之间的距离来进行,是一种 非参数技术 。 对于模板中的目标,即
如果你真的就看完了《 packetdrill框架点滴剖析以及TCP重传的一个细节 》,我觉得你应该有一个疑问,那就是RH发行版使用的2.6.32内核真的使用了PRR降窗算法吗?为此,我把故事再撸一遍。         按照标准的2.6.32内核,第一次收到SACK的时候,在进入重传之前,拥塞窗口的值应该是in_flight+1,即2+1=3个段,也就是说可以重传1个段,但是抓包发现重传了2个段,tcpprobe也证实了在进入重传之前,拥塞窗口的值是4,而in_flight的值为2,完全对不上tcp_cwnd
OSSEC linux(server) windows(agent)史上最详细中文配置 作者:谭丙章 E-mail:feifengwind@163.com ossec官方网站: http://www.ossec.net/ ossec帮助文档: http://ossec-docs.readthedocs.org/en/latest/manual/index.html 介绍 SSEC是一款开源的多平台的入侵检测系统,可以运行于Windows, Linux, OpenBSD/FreeBSD, 以及 MacOS等

反汇编mbr - 2016-07-22 19:07:23

https://notelzg.github.io/2016/07/17/disamy-mbr/ 个人博客,排版更好 今天把我们每次开机都用到的MBR,反汇编看看里面的引导代码是怎么样的顺便说一下 MBR 相关的开机过程。以及我们经常用的U盘自启动 程序到底值怎么玩的。 window开机流程 流程 加电--BIOS-- MBR-- DPT-- pbr-- Bootmgr-- bcd-- Winload.exe--- —- 内核加载– 整个windows7系统 细节 在CPU上电之后,若由硬盘启动,则BIO
Members 成员 Members are used for registering and authenticating external users of an Umbraco installation (ie. forum members, intranet users and so forth). Unlike with Document Types and Media Types everything is done in the Members section both defining a