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方面的相关内容,希望能给大家带来收获.

时间: 2024-12-25 03:32:25

HDFS的StartupProgress启动跟踪分析的相关文章

20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程

回顾 1.中断上下文的切换——保存现场&恢复现场 本节主要课程内容 Linux内核源代码简介 1.打开内核源代码页面 arch/目录:支持不同CPU的源代码:其中的X86是重点 init/目录:内核启动相关的代码基本都在该目录中(比如main.c等) start_kernel函数就相当于普通C程序的main函数 kernel/目录:Linux内核核心代码在kernel目录中 README 介绍了什么是Linux,Linux能够在哪些硬件上运行,如何安装内核源代码等 构造一个简单的linux系统m

跟踪分析Linux内核的启动过程--实验报告 分析 及知识重点

跟踪分析Linux内核的启动过程 攥写人:杨光  学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ) 知识总结: ****Linux内核中关键目录: arch:不同cpu的支持,我们主要关注的是其中x86文件夹 init:内核启动的相关代码,期中main.c是内核启动的起点,main.c中的start_kernel是内核初始化的起点 ker

实验三:跟踪分析Linux内核的启动过程 ----- 20135108 李泽源

实验要求: 使用gdb跟踪调试内核从start_kernel到init进程启动 详细分析从start_kernel到init进程启动的过程并结合实验截图撰写一篇署名博客,并在博客文章中注明“真实姓名(与最后申请证书的姓名务必一致) + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,博客内容的具体要求如下: 题目自拟,内容围绕Linux内核的启动过程,即从start_kernel

Linux内核设计第八周学习总结 理解进程调度时机跟踪分析进程调度与进程切换的过程

陈巧然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.视频内容 Linux系统的一般执行过程 最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程 1. 正在运行的用户态进程X 2. 发生中断——save cs:eip/esp/eflags(current) to kernel stack, then load cs:eip(entry of a specific IS

TaintDroid剖析之Native方法级污点跟踪分析

1.Native方法的污点传播 在前两篇文章中我们详细分析了TaintDroid对DVM栈帧的修改,以及它是如何在修改之后的栈帧中实现DVM变量级污点跟踪的.现在我们继续分析其第二个粒度的污点跟踪——Native方法级跟踪. 回顾前文,我们知道Native方法执行在Native栈帧中,且Native栈帧由dvmPushJNIFrame函数分配栈空间,再由dvmCallMethodV/A或者dvmInvokeMethod对栈帧进行初始化,所以我们也按照之前的方式进行Native方法级跟踪机制分析.

YARN Container 启动流程分析

YARN Container 启动流程分析 本文档从代码出发,分析了 YARN 中 Container 启动的整个过程,希望给出这个过程的一个整体的概念. 文档分为两个部分:第一部分是全局,从头至尾地把 Container 启动的整个流程串联起来:第二部分是细节,简要分析了 Container 启动流程中涉及到的服务.接口和类. 注意: 基于 hadoop-2.6.0 的代码 只写了与 Container 启动相关的逻辑,并且还大量忽略了很多细节,目的是为了得到一个整体的概念. 为了让分析更具体

2019-举例跟踪分析Linux内核5.0系统调用处理过程

简介 学号520 实验环境基于ubuntu18.04 选择系统调用号20 getpid()分析 实验目的 学会使用gdb工具跟踪linux内核函数调用 学会使用C代码和嵌入式汇编使用系统中断 分析system_call中断处理过程 实验步骤 1.下载linux5.0.1内核并编译 wget https://mirrors.aliyun.com/linux-kernel/v5.x/linux-5.0.1.tar.xz xz -d linux-5.0.1.tar.xz tar -xvf linux-

SpringBoot启动流程分析(四):IoC容器的初始化过程

SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一):SpringApplication类初始化过程 SpringBoot启动流程分析(二):SpringApplication的run方法 SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法 SpringBoot启动流程分析(四

u-boot启动流程分析(2)_板级(board)部分

转自:http://www.wowotech.net/u-boot/boot_flow_2.html 目录: 1. 前言 2. Generic Board 3. _main 4. global data介绍以及背后的思考 5. 前置的板级初始化操作 6. u-boot的relocation 7. 后置的板级初始化操作 1. 前言 书接上文(u-boot启动流程分析(1)_平台相关部分),本文介绍u-boot启动流程中和具体版型(board)有关的部分,也即board_init_f/board_i