hadoop原理和机制

1、

SecondaryNameNode工作原理

namenode:首先来说对于每个文件操作,Hadoop并不会都写到fsimage,这样是很慢的,但是每次操作在提交后运行前先写入edits编辑日志,当edits编辑日志文件大小超过64M(参数可以设定),或者时间超过1小时(参数可以设定),secondarynamenode就会做checkpoint的工作,这时namenode产生临时空文件edits.new,secondarynamenode就会读取namenode中的edits和fsimage,然后进行合并,合并成fsimage.ckpt检查点,然后通过HTTP方式将fsimage.ckpt发送到NameNode,然后NameNode把fsimage.ckpt重命名为fsimage(覆盖原有fsimage文件),同时edits.new重命名为edits(覆盖原有edits文件)。

注意这里edits.new是个临时文件,只有NameNode或者SecondaryNameNode正在做checkpoint的时候存在。

namenode启动读取fsimage原理

当重新启动namenode的时候,NameNode启动时根据checkpoint时间加载最新的fsimage和edits文件到内存里,然后创建文件edits.new临时空文件,然后合并生成fsimage.ckpt检查点,edits.new重命名为edits(覆盖原有edits文件),fsimage.ckpt重命名为fsimage(覆盖原有fsimage文件),然后更新fstime时间 和VERSION版本

hadoop原理和机制_第1张图片

------------------------------------------------网上资料-------------------------------------------------------

Hadoop学习笔记之:HDFS体系架构 

地址:http://www.thebigdata.cn/HBase/11829.html

[日期:2014-09-17] 来源:燕子覃的测试专栏博客  作者: [字体:大 中 小]

HDFS简介

      HDFS有着高容错性fault-tolerant)的特点,并且设计用来部署在低廉的(low-cost)硬件上。而且它提供高吞吐量(high throughput)来访问应用程序的数据,适合那些有着超大数据集(large data set)的应用程序。

      1.  HDFS有以下几个主要特点:

     处理超大文件:存储的一个超大文件可以达到数GB级、数TB级、数PB级。

     集群规模动态扩展:节点动态加入到集群,可以数百数千个

     流式数据读写:HDFS的设计思想“一次写入,多次读取”,一个数据集一旦由数据源生成,就会被复制分发到不同的存储节点中,然后响应各种各样的数据分析任务请求。

      运行于廉价的商用机器集群上:HDFS设计时充分考虑了可靠性、安全性及高可用性,因此Hadoop对硬件要求比较低,可以运行于廉价的商用机器集群,无需昂贵的高可用性机器

      2.HDFS的局限性:

    不适合低延迟数据访问: HDFS是为了处理大型数据集,主要是为了达到高的数据吞吐量而设计,这就可能以高延迟作为代价。10毫秒以下的访问可以无视hdfs,不过hbase可以弥补这个缺

    无法高效存储大量小文件: namenode节点在内存中存储住整个文件系统的元数据,因此文件的数量就会受到限制,每个文件的元数据大约150字节

                不支持多用户写入及任意修改文件  :不支持多用户对同一文件进行操作,而且写操作只能在文件末尾完成,即追加操作。

HDFS体系结构

HDFS的基本概念:

        块(block):

  • HDFS的文件以块的方式存储,块的大小默认为64MB。大于多数文件系统的块的大小。通常文件系统的块的大小为几千字节,磁盘块的大小为512B

  • 比磁盘块大很多,目的是减少寻址开销。如果块太小,大量的时间将花在磁盘块的定位时间上。

  • HDFS文件小于块大小时,不会占满整个数据块的存储空间 ??

HDFS体系结构说明hadoop原理和机制_第2张图片

  • HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanode组成。

  • Namenode是一个中心服务器,负责管理文件系统的命名空间和客户端对文件的访问。

  • Namenode执行文件系统的命名空间操作,例如打开、关闭、重命名文件和目录,同时决定block到具体Datanode节点的映射。

  • Datanode负责处理文件系统的读写请求,在Namenode的指挥下进行block的创建、删除和复制

  • 一个文件其实分成一个或多个block,这些block存储在Datanode集合里。

NameNode

  • NameNode作用:负责管理文件系统的命名空间(元数据),维护整个文件系统的文件目录树及这些文件的索引目录。

  • NameNode的文件结构(图示引用书籍:Hadoop实战)

wKioL1P5qdLjrcZaAAAv5gp5q94308.jpg

      fsimage二进制文件,存储HDFS文件和目录元数据

      Edits二进制文件,每次保存fsimage之后到下次保存之间的所有HDFS操作,记录在Edit s文件。对文件的每一次操作,如打开、关闭、重命名文件和目录,都会生成一个edit记录。

      fstime:二进制文件,fsimage做完一次checkpoint后,将最新的时间戳写入到fstime

  • VERSION文本文件,文件的内容为(图示引用书籍:Hadoop实战)

wKiom1P5qMzymOryAABPAzxRgPA901.jpg

      其中,namespaceID是文件系统的唯一标识符,当文件系统第一次被格式化的时候会被创建,这个标识符也要求所有的DataNode节点和NameNode保持一致。  NameNode会使用它识别新的DataNodeDataNode只有在向NameNode注册后才会获取namespaceID

  • 元数据

    包括文件和目录的ownershippermission

    文件包含哪些块,块的个数及块的副本数;

    块保存在哪个Datanode(由Datanode启动时上报);

      fsimage中的元数据结构如图所示:

wKioL1P5qfWxTfgNAAC_P5hc_EM937.jpg

  • 元数据分类:分为内存元数据和元数据文件

    元数据文件:包含fsimage&edits,存储在本地磁盘和NFS,防止NameNode所在机器磁盘坏掉后数据丢失

    内存元数据:包含fsimageBlockmap的映像。NameNode启动时会加载fsimage&edits文件到内存,merge后将最新的fsimage回写到本地磁盘和NFS,覆盖旧的fsimage文件

 

  • NameNode启动过程中fsimage文件处理流程

      第一步:首先加载硬盘上的fsimage文件和edits文件,在内存中merge后将新的fsimage写到磁盘上,这个过程叫checkpoint

      (一般NameNode会配置两个目录来存放fsimageedits文件,分别是本地磁盘和NFS,防止NameNode所在机器的磁盘坏掉后数据丢失。

      NameNode启动时会比较NFS和本地磁盘中的fstime中记载的checkpoint时间加载最新的fsimage。)

      第二步:NameNode加载完fsimage&edits文件后,会将merge后的结果同时写到本地磁盘和NFS此时磁盘上有一份原始的fsimage文件和一份checkpoint文件:fsimage.ckpt。同时edits文件为空。

      第三步:写完checkpoint后,将fsimage.ckpt改名为fsimage(覆盖原有的fsimage),并将最新时间戳写入fstime文件

      DataNode

  • DataNode的作用:

    保存block

    启动DataNode线程的时候会向NameNode汇报block信息

    通过向NameNode发送心跳保持与其联系(3秒一次),如果NameNode10分钟没有收到DataNode的心跳,则认为其已经lost,并copy其上的block到其它DataNode

  • DataNode的文件结构(图示引用书籍:Hadoop实战)

hadoop原理和机制_第3张图片

 

      Blk_refixHDFS中的文件数据块,存储的是原始文件内容

      Blk_refix.meta:块的元数据文件:包括版本和类型信息的头文件,与一系列块的的区域校验和组成。

      VERSION文本文件,文件的内容为:

hadoop原理和机制_第4张图片

      其中NamesopaceIDcTimelayoutVersionNameNode保持一致,namespaceID是第一次连接NameNode获得的。storageType对于DataNode来说是唯一的,用于NameNode表示DataNode

  • DataNode启动过程

    datanode启动时,每个datanode对本地磁盘进行扫描,将本datanode上保存的block信息汇报给namenode

    namenode在接收到每个datanode的块信息汇报后,将接收到的块信息,以及其所在的datanode信息等保存在内存中。

    Namenodeblock ->datanodes list的对应表信息保存在BlocksMap(如图所示)中hadoop原理和机制_第5张图片

      Secondary NameNode

      为了提高NameNode的可靠性,从Hadoop 0.23开始引入了Secondary NameNode

  • Secondary NameNode的作用

      FsimageHDFS存储元数据的文件,它不会在HDFS的每次文件操作(如打开、查询、创建、修改文件)后进行更新。而HDFS的每一次文件操作会增加一条edits记录。这样会出现edits记录不断增加的情况。

      这种设计不影响系统的恢复能力。因为如果Namenode失败了,元数据的最新状态可以通过从磁盘中读出fsimage文件加载到内存中来进行重新恢复,然后重新执行edits记录中的操作,这也正是NameNode重新启动时所做的事情。但是如果edits记录很多,NameNode启动时会花很长的时间来运行edits记录中的操作。在此期间,HDFS文件系统是不可用的。

      为了解决这个问题,HadoopNameNode之外的节点上运行了一个Secondary NameNode进程。Secondary NameNode定期从NameNode拷贝fsimageedits记录到临时目录并合并成一个新的Fsimage,随后它将新的fsimage上传到NameNode,这样NameNode便会更新fsimage并删除原来的编辑日志。这个过程叫checkpoint。具体过程如下:

hadoop原理和机制_第6张图片

说明:

      第一步:Secondary NameNode首先请求NameNode进行edits的滚动,这样NameNode开始重新写一个新的edit log

      第二步:Secondary NameNode通过HTTP方式读取NameNode中的fsimageedits

      第三步:Secondary NameNode读取fsimage到内存中,然后执行edits中的每个操作,并创建一个新的统一的fsimage文件。

      第四步:Secondary NameNode通过HTTP方式将新的fsimage发送到NameNode

      第五步:NameNode用新的fsimage替换旧的fsimage,旧的edits文件用步骤1中的edits进行替换,同时系统会更新fsimage文件记录检查点时间

  • Secondary NameNode的文件结构(图示引用书籍:Hadoop实战)

hadoop原理和机制_第7张图片

  • Secondary NameNode不足之处:

    因为Secondary namenode并不是实时进行checkpoint,所以当还没有进行下一次checkpoint的时候namenode出现了硬件故障同时又没有通过NFS存储元数据,那么Namenode中自上次checkpoint之后到故障发生期间的所有edits文件将丢失。因为此时secondary namenode存的只有上一次的fsimage文件,没有最新的edits文件,无法通过secondary namenode进行这段时间内的数据恢复。

    Secondary NameNode不是NameNode的备份进程,如果NameNode宕机了,而SecondaryNameNode没有宕机,集群照样不能正常工作。如果要恢复集群工作,需要手动将Secondary NameNode上的fsimage文件拷贝到新的NameNode上面。

      为了解决以上问题,从Hadoop2.0开始,引入了高可用HA NameNode




NameNode启动中image文件处理流程

地址:http://blog.csdn.net/liangliyin/article/details/6370782

分类: Hadoop   852人阅读  评论(0)  收藏  举报
image 磁盘 merge

      NameNode时与image文件相关的大概有下面三步操作:

 

第一步  加载image

NameNode启动后时首先加载硬盘上的fsimage文件(保持了整个命名空间)和edits文件(保持了命名空间的操作日志),在内存中merge后将新的fsimage写到磁盘上,即做一次checkpoint。

其中加载过程如图1所示:

 


 

                                                  图1 加载image文件流程

通常NameNode配置两个目录来存放fsimage&edits文件,分布是本地磁盘和NFS,防止NameNode所在机器磁盘坏掉后数据丢失。

每个目录下都保持了一个fstime文件,里面记录了最近一次checkpoint时间。NameNode启动时根据checkpoint时间加载最新的一份数据。

edits.new是个临时文件,只有SecondaryNameNode正在做checkpoint的时候存在。

 

     第二步 保存image

    NameNode加载完fsimage&edits文件后,会将merge后的结果写到磁盘上。写的过程会对本地磁盘和NFS 依次 做图2所示的操作。

 

 

                                       图2 保存image文件流程

这一步做完后,磁盘上有一份原始的fsimage文件,一份最新checkpoint文件:fsimage.ckpt,另外edits&edits.new为空文件(4个字节)。

 

    第三步 滚动image文件

写完checkpoint后,NameNode会对磁盘上的文件做一次滚动(重命名),如图3所示:

 

 

                                     图3 滚动image文件流程

该步骤将fsimage.ckpt重命名为fsimage(覆盖原有fsimage文件),edits.new重命名为edits(覆盖原有edits文件),并将最新时间戳写入fstime文件。




Hadoop NameNode的元数据持久化存储FSImage和日志存储EditLog源代码分析

地址:http://blog.csdn.net/lskyne/article/details/8843367
分类: Hadoop   1399人阅读  评论(0)  收藏  举报

HDFS NameNode High Availability中一个关键的问题就是Editlog如何保存,怎么才能保证在Active和Standby的NameNode切换时Editlog不丢失记录,也不会重复计算。这就需要对NameNode的元数据持久化机制(metadata persistent storage)有比较深的理解。目前Hadoop EditLogs Re-write由Cloudera的工程师发起重构,有将近10000行代码,对整个EditLog整体架构进行重写,以适应Hadoop的进化。

目前HDFS的EditLog文件可以存放在多种容器里,比如Local Filesystem, shared NFS, Bookkeeper等(其对应的日志管理接口分别定义在FileJournalManager,BookkeeperJournalManager,BackupJournalManager等),而对应的管理这些不同容器内的文件的方法也有多种。目前主要是采用了基于transactionId的日志管理方法(FSImageTransactionalStorageInspector这个类是具体的实现方法)。这篇文章从NameNode的启动代码来分析metadata persistent storage。

例如,我们一般用如下命令格式化文件系统:

bin/hdfs namenode –format –clusterid eric

这个过程的函数调用关系如下图所示:

Main()->createNameNode()静态方法创建NameNode实例->根据参数进入format函数。

private static boolean format(Configuration conf, boolean force,
      boolean isInteractive) throws IOException {
    //首先是一系列的参数初始化,例如nsId(nameserviceid),namenodeId
    String nsId = DFSUtil.getNamenodeNameServiceId(conf);
    String namenodeId = HAUtil.getNameNodeId(conf, nsId);
    initializeGenericKeys(conf, nsId, namenodeId);
    checkAllowFormat(conf);
    //获取存放FsImage(dfs.namenode.name.dir)
    //和EditLog(包括dfs.namenode.shared.edit.dir和dfs.namenode.edit.dir)的目录。
    Collection dirsToFormat = FSNamesystem.getNamespaceDirs(conf);
    List editDirsToFormat =
                 FSNamesystem.getNamespaceEditsDirs(conf);
    if (!confirmFormat(dirsToFormat, force, isInteractive)) {
      return true; // aborted
    }

    // if clusterID is not provided - see if you can find the current one
    String clusterId = StartupOption.FORMAT.getClusterId();
    if(clusterId == null || clusterId.equals("")) {
      //Generate a new cluster id
      clusterId = NNStorage.newClusterID();
    }
    System.out.println("Formatting using clusterid: " + clusterId);
    //下面三行开始正式创建FSImage,EditLog,FSNamesystem,然后把元数据写入磁盘文件。
    FSImage fsImage = new FSImage(conf, dirsToFormat, editDirsToFormat);
    FSNamesystem fsn = new FSNamesystem(conf, fsImage);
    fsImage.format(fsn, clusterId);
    return false;
  }

然后我们分别看看最下面这三行代码是怎么创建和格式化文件系统的。

protected FSImage(Configuration conf,
                    Collection imageDirs,
                    List editsDirs)
      throws IOException {
    this.conf = conf;
    //storage用于管理NameNode的元数据持久化存储在本地文件系统的文件和目录。
    storage = new NNStorage(conf, imageDirs, editsDirs);
    if(conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_KEY,
                       DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_DEFAULT)) {
      storage.setRestoreFailedStorage(true);
    }
    //声明与此FSImage相关的EditLog对象(包括shared和local)。
    this.editLog = new FSEditLog(conf, storage, editsDirs);
    String nameserviceId = DFSUtil.getNamenodeNameServiceId(conf);
    //下面这个判断比较关键,如果HA机制没有开启,那么直接initJournalsForWrite()
    //如果HA机制开启,那么initSharedJournalsForRead()
    if (!HAUtil.isHAEnabled(conf, nameserviceId)) {
      editLog.initJournalsForWrite();
    } else {
      editLog.initSharedJournalsForRead();
    }

    archivalManager = new NNStorageRetentionManager(conf, storage, editLog);
  }

我们重点看下EditLog是怎么initJournalsForWrite()和initSharedJournalsForRead()的。

特意把这两个函数放在一起对照着看,因为新版的HDFS已经把EditLog的不同时期划分为不同的状态。目前有以下几种状态:

private enum State {
    UNINITIALIZED,
    BETWEEN_LOG_SEGMENTS,
    IN_SEGMENT,
    OPEN_FOR_READING,
    CLOSED;
  }

这两个函数首先都是检查EditLog的状态,然后初始化Journals,最后设置成新的状态。要特别注意初始化Journal和打开Journal的区别。

对于非HA机制的情况下,EditLog应该开始于UNINITIALIZED或者CLOSED状态(因为在构造对象时,EditLog的成员变量state默认为State.UNINITIALIZED)。初始化完成之后进入BETWEEN_LOG_SEGMENTS状态,表示前一个segment已经关闭,新的还没开始,已经做好准备了。在后面打开服务的时候会变成IN_SEGMENT状态,表示可以写EditLog日志了。

对于HA机制的情况下,EditLog同样应该开始于UNINITIALIZED或者CLOSED状态,但是在完成初始化后并不进入BETWEEN_LOG_SEGMENTS状态,而是进入OPEN_FOR_READING状态(因为目前NameNode启动的时候都是以Standby模式启动的,然后通过dfsHAAdmin发送命令把其中一个Standby的NameNode转化成Active的)。

public synchronized void initJournalsForWrite() {
    Preconditions.checkState(state == State.UNINITIALIZED ||
        state == State.CLOSED, "Unexpected state: %s", state);

    initJournals(this.editsDirs);
    state = State.BETWEEN_LOG_SEGMENTS;
  }

  public synchronized void initSharedJournalsForRead() {
    if (state == State.OPEN_FOR_READING) {
      LOG.warn("Initializing shared journals for READ, already open for READ",
          new Exception());
      return;
    }
    Preconditions.checkState(state == State.UNINITIALIZED ||
        state == State.CLOSED);

    initJournals(this.sharedEditsDirs);
    state = State.OPEN_FOR_READING;
  }

这两个函数都调用了initJournals(List dirs)这个函数用于初始化日志系统。

private synchronized void initJournals(List dirs) {
    int minimumRedundantJournals = conf.getInt(
        DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_KEY,
        DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_DEFAULT);
    //JournalSet就是存放一系列的JournalAndStream的容器
    //对于容器中的一个元素JournalAndStream表示一个JournalManager和一个输出流
    //JournalManager有多种实现,例如FileJournalManager,
    //BookkeeperJournalManager,BackupJournalManager等。
    journalSet = new JournalSet(minimumRedundantJournals);
    for (URI u : dirs) {
      boolean required = FSNamesystem.getRequiredNamespaceEditsDirs(conf)
          .contains(u);
      //对于这些dirs,如果从scheme中得知其是本地文件系统的目录,
      //那么这个Journal对应的JournalManager为FileJournalManager,并把其加入JournalSet
      if (u.getScheme().equals(NNStorage.LOCAL_URI_SCHEME)) {
        StorageDirectory sd = storage.getStorageDirectory(u);
        if (sd != null) {
          journalSet.add(new FileJournalManager(sd, storage), required);
        }
      } else {
        //如果不是本地文件,有可能是BookKeeperJournalManager或者类似的插件式JournalManager,
        //那么根据配置文件dfs.namenode.edits.journal-plugin.*生成对应的JournalManager。
        journalSet.add(createJournal(u), required);
      }
    }

    if (journalSet.isEmpty()) {
      LOG.error("No edits directories configured!");
    }
  }

至此FSImage fsImage = new FSImage(conf, dirsToFormat, editDirsToFormat);这行代码所涉及到的内容分析完毕。

然后看FSNamesystem fsn = new FSNamesystem(conf, fsImage);这行代码都干了啥。FSNamesystem这个构造函数在FSNamesystem.java的411-482行,首先获取resourceRecheckInterval,生成BlockManager对象、usergroup信息、supergroup等。然后设置了一个很重要的变量persistBlocks。我们都知道在hadoop中Block location信息是启动时由DataNode向NameNode汇报的,并没有持久化。但是这里增加了这个参数,并且在开启HA机制时,persistBlocks设置为true,也就是在shared edit directory中保存block location的信息。这个难道是为了节省启动集群时block report的时间?但是这样做和Hadoop/GFS的初衷就不一样了。(注:后来进一步分析代码得知,这里的persistBlocks是持久化block的元数据,例如GS、大小等,但是并不包括block的replica都分布在哪些DataNode上,这个信息还是得靠DataNode report给NameNode的) 然后就是设置这个HDFS文件系统的一些默认参数(blockSize,bytesPerChecksum,writePacketSize,replication,fileBufferSize)和一系列文件系统相关变量信息。最后调用this.dir = new FSDirectory(fsImage, this, conf);这行代码生成目录树相关的信息。然后调用fsImage.format(fsn, clusterId);把这些元数据信息持久化到dfs.namenode.name.dir,dfs.namenode.edit.dir,dfs.namenode,shared.edit.dir中。生成以下文件:

我们知道FSImage是格式化时生成的或者由NameNode定期在后台checkpoint出来的,不是每次操作都涉及到FSImage的变化;而EditLog是与client的每次RPC操作紧密相关的,每次EditOp的变化也是与我们前面提到的transactionId的变化紧密相关的。那么在我们格式化完NameNode之后,启动NameNode时,这个EditLog的状态是怎么变换的就比较重要了。

在正常启动NameNode时,函数调用关系是:

NameNode.main()->NameNode.createNameNode()->NameNode.NameNode()->NameNode.initialize()->NameNode.loadNamesystem()->FSNamesystem.loadFromDisk()

在FSNamesystem.loadFromDisk()函数中同样会new FSImage和FSNamesystem对象,和前面讲format的流程是一样的。不同的是在这之后会调用Namesystem.loadFSImage(startOpt,fsImage, HAUtil.isHAEnabled(conf,nameserviceId))来加载已有的文件系统镜像。

void loadFSImage(StartupOption startOpt, FSImage fsImage, boolean haEnabled)
      throws IOException {
    // format before starting up if requested
    if (startOpt == StartupOption.FORMAT) {
      fsImage.format(this, fsImage.getStorage().determineClusterId());
      startOpt = StartupOption.REGULAR;
    }
    boolean success = false;
    writeLock();
    try {
      // We shouldn't be calling saveNamespace if we've come up in standby state.
      MetaRecoveryContext recovery = startOpt.createRecoveryContext();
      //这个fsImage.recoverTransitionRead()函数首先会做些update,import,rollback方面的工作。
      //对于我们这种启动参数regular的,会调用FsImage.loadFSImage()函数。
      if (fsImage.recoverTransitionRead(startOpt, this, recovery) && !haEnabled) {
        fsImage.saveNamespace(this);
      }
      // This will start a new log segment and write to the seen_txid file, so
      // we shouldn't do it when coming up in standby state
      // 非HA模式下,因为在前面format的时候已经调用了initJournalsForWrite,
      // EditLog进入State.BETWEEN_LOG_SEGMENTS状态。
      // 在此函数里进一步更改状态进入State.IN_SEGMENT状态。
      // 在HA模式,这个状态变化在FSNamesystem.startActiveServices()这个函数中。
      if (!haEnabled) {
        fsImage.openEditLogForWrite();
      }

      success = true;
    } finally {
      if (!success) {
        fsImage.close();
      }
      writeUnlock();
    }
    dir.imageLoadComplete();
  }

其中fsImage.recoverTransitionRead(startOpt, this, recovery)会调用到FsImage.loadFSImage()函数。这个FsImage.loadFSImage()函数选择最新的image文件加载并与在它之后生产的EditLog文件merge成新的FSImage文件。

boolean loadFSImage(FSNamesystem target, MetaRecoveryContext recovery)
      throws IOException {
    FSImageStorageInspector inspector = storage.readAndInspectDirs();

    isUpgradeFinalized = inspector.isUpgradeFinalized();
    //真正调用的是FSImageTransactionalStorageInspector.getLastestImage()获取最新的Image
    FSImageStorageInspector.FSImageFile imageFile
      = inspector.getLatestImage();
    boolean needToSave = inspector.needToSave();

    Iterable editStreams = null;

    if (editLog.isOpenForWrite()) {
      // We only want to recover streams if we're going into Active mode.
      editLog.recoverUnclosedStreams();
    }
    if (LayoutVersion.supports(Feature.TXID_BASED_LAYOUT,
                               getLayoutVersion())) {
      // If we're open for write, we're either non-HA or we're the active NN, so
      // we better be able to load all the edits. If we're the standby NN, it's
      // OK to not be able to read all of edits right now.
      long toAtLeastTxId = editLog.isOpenForWrite() ? inspector.getMaxSeenTxId() : 0;
      // 选择从imageFile.getcheckpointTxId()+1到toAtLeastTxId这些TxId所对应的EditLog文件
      // 作为与当前FSImage文件merge的输入流
      editStreams = editLog.selectInputStreams(imageFile.getCheckpointTxId() + 1,
          toAtLeastTxId, false);
    } else {
      editStreams = FSImagePreTransactionalStorageInspector
        .getEditLogStreams(storage);
    }

    LOG.debug("Planning to load image :\n" + imageFile);
    for (EditLogInputStream l : editStreams) {
      LOG.debug("\t Planning to load edit stream: " + l);
    }

    try {
      StorageDirectory sdForProperties = imageFile.sd;
      storage.readProperties(sdForProperties);

      if (LayoutVersion.supports(Feature.TXID_BASED_LAYOUT,
                                 getLayoutVersion())) {
        // For txid-based layout, we should have a .md5 file
        // next to the image file
        // 这个函数调用FSImage的loader(FSImageFormat.load()函数)加载文件系统元数据到内存中
        loadFSImage(imageFile.getFile(), target, recovery);
      } else if (LayoutVersion.supports(Feature.FSIMAGE_CHECKSUM,
                                        getLayoutVersion())) {
        // In 0.22, we have the checksum stored in the VERSION file.
        String md5 = storage.getDeprecatedProperty(
            NNStorage.DEPRECATED_MESSAGE_DIGEST_PROPERTY);
        if (md5 == null) {
          throw new InconsistentFSStateException(sdForProperties.getRoot(),
              "Message digest property " +
              NNStorage.DEPRECATED_MESSAGE_DIGEST_PROPERTY +
              " not set for storage directory " + sdForProperties.getRoot());
        }
        loadFSImage(imageFile.getFile(), new MD5Hash(md5), target, recovery);
      } else {
        // We don't have any record of the md5sum
        loadFSImage(imageFile.getFile(), null, target, recovery);
      }
    } catch (IOException ioe) {
      FSEditLog.closeAllStreams(editStreams);
      throw new IOException("Failed to load image from " + imageFile, ioe);
    }
    //在这从我们找到的editStreams输入流中输入EditLog并且在内存中merge成新的FSImage
    long txnsAdvanced = loadEdits(editStreams, target, recovery);
    //如果上一步merge了新的EditLog,就需要持久化到硬盘成新的FSImage。
    needToSave |= needsResaveBasedOnStaleCheckpoint(imageFile.getFile(),
                                                    txnsAdvanced);
    editLog.setNextTxId(lastAppliedTxId + 1);
    return needToSave;
  }

这里面很重要的就是transactionId是怎么变化的。在format之后,文件系统的元数据目录/dfs/name/current下是这样的结构:

Seen_txid就是存放transactionId的文件,format之后是0。但是当文件系统运行了一段时间之后,就会变成类似的样子:

这时候seen_txid里存放的数据时65,也就是现在正在进行的EditOp的txid是65。

然后看看在非HA模式下EditLog是怎么从State.BETWEEN_LOG_SEGMENTS到State.IN_SEGMENT转化的。这个过程是通过openEditLogForWrite()这个函数完成的。

void openEditLogForWrite() throws IOException {
    assert editLog != null : "editLog must be initialized";
    //这个函数负责检查transactionId的合法性,并打开edits_*****文件输出流
    editLog.openForWrite();
    //既然上面已经打开了一个editlog输出流,那么需要把当前的transactionId写到seen_txid文件中。
    storage.writeTransactionIdFileToStorage(editLog.getCurSegmentTxId());
  };

synchronized void openForWrite() throws IOException {
    Preconditions.checkState(state == State.BETWEEN_LOG_SEGMENTS,
        "Bad state: %s", state);
    //getLastWrittenTxId获取已经写到日志文件中的最后的transactionId,
    //对于上图中的例子返回的是64(不是65),segmentTxId是65
    long segmentTxId = getLastWrittenTxId() + 1;
    // Safety check: we should never start a segment if there are
    // newer txids readable.
    // 要检查有没有比segmentTxId更大的Id已经写到日志了,因为我们要开始一个以segmentTxId为开始的
    // segment,如果有更大的Id已经写到日志就会出现两个日志TxId交叉的情况。
    // 下面这行函数就是通过提供segmentTxId看是否有edits_******的文件中包含这个transactionId的,
    // 如果有的话就说明不能以这个Id开始一个editlog segment
    EditLogInputStream s = journalSet.getInputStream(segmentTxId, true);
    try {
      Preconditions.checkState(s == null,
          "Cannot start writing at txid %s when there is a stream " +
          "available for read: %s", segmentTxId, s);
    } finally {
      IOUtils.closeStream(s);
    }
    // 到这了,说明可以以segmentTxId为起点开启一个edits_*****的文件。
    startLogSegmentAndWriteHeaderTxn(segmentTxId);
    assert state == State.IN_SEGMENT : "Bad state: " + state;
  }

在startLogSegmentAndWriteHeaderTxn这个函数里面,EditLog的状态从BETWEEN_LOG_SEGMENTS转化成了IN_SEGMENT状态,并且开启了与对应的edits_*****的文件输出流。然后把我们的这个开启日志段(OP_START_LOG_SEGMENT)的操作记录在这个流对应的文件中。

synchronized void startLogSegmentAndWriteHeaderTxn(final long segmentTxId
      ) throws IOException {
    startLogSegment(segmentTxId);

    logEdit(LogSegmentOp.getInstance(cache.get(),
        FSEditLogOpCodes.OP_START_LOG_SEGMENT));
    logSync();
  }

前面已经说过,对于开启HA模式的EditLog的状态变化是在startActiveServices函数中完成的。所以在startActiveServices这个函数之后,EditLog文件就进入IN_SEGMENT状态,可以接受写入了。那么这个时候NameNode就可以接收来自client和DataNode的RPC请求并执行这些操作了。

void startActiveServices() throws IOException {
    LOG.info("Starting services required for active state");
    writeLock();
    try {
      FSEditLog editLog = dir.fsImage.getEditLog();

      if (!editLog.isOpenForWrite()) {
        //由于在initialization的时候,我们已经把共享目录下的EditLog的状态设为OPEN_FOR_READING
        //所以此时必须进入这个if分支来进一步初始化editlog
        editLog.initJournalsForWrite();
        // May need to recover
        editLog.recoverUnclosedStreams();

        LOG.info("Catching up to latest edits from old active before " +
            "taking over writer role in edits logs.");
        editLogTailer.catchupDuringFailover();

        LOG.info("Reprocessing replication and invalidation queues...");
        blockManager.getDatanodeManager().markAllDatanodesStale();
        blockManager.clearQueues();
        blockManager.processAllPendingDNMessages();
        blockManager.processMisReplicatedBlocks();

        if (LOG.isDebugEnabled()) {
          LOG.debug("NameNode metadata after re-processing " +
              "replication and invalidation queues during failover:\n" +
              metaSaveAsString());
        }

        long nextTxId = dir.fsImage.getLastAppliedTxId() + 1;
        LOG.info("Will take over writing edit logs at txnid " +
            nextTxId);
        editLog.setNextTxId(nextTxId);

        dir.fsImage.editLog.openForWrite();
      }
      if (haEnabled) {
        // Renew all of the leases before becoming active.
        // This is because, while we were in standby mode,
        // the leases weren't getting renewed on this NN.
        // Give them all a fresh start here.
        leaseManager.renewAllLeases();
      }
      leaseManager.startMonitor();
      startSecretManagerIfNecessary();
    } finally {
      writeUnlock();
    }
  }



NameNode节点加载FSImage+EditsLog

地址:http://blog.csdn.net/xhh198781/article/details/6997677

分类: HDFS源码解析   1155人阅读  评论(0)  收藏  举报
image

      在前面的博文中,我曾多次提到过在NameNode的启动过程中有加载FSImage+EditsLog这一必不可少的一项。关于文件fsImage和文件edits是用来存放神马的,我在这里就不用在重复了吧。在本文我将详细的叙述NameNode是如何加载fsimage和edits文件的。

      在NameNode节点启动之前,我们一般会在配置文件hdfs-default.xml中分别配置文件fsImage、edits所在的路径,它们对应配置文件中的项dsf.name.dir、dfs.name.edits.dir。同时,也可以同时配置多个fsImage、edits的路径,其中fsImage和edits也可以有相同的路径。无论是文件fsImage的路径还是文件edits的路径,NameNode都会把它抽象成一个StorageDirectory对象:

因此,每一fsimage的路径被实例化为IMAGE类型的StorageDirectory对象,每一个edits的路径被实例化为EDITS类型的StorageDirectory对象,而既是fsimage又是edits的路径则被实例化为IMAGE_AND_EDITS类型的StorageDirectory对象。对于配置了多个fsimage和edits的路径的启动,NameNode并不会加载加载,而是从IMAGE类型的StorageDirectory集合中挑选一个最新版本的fsimage文件,从EDITS类型的StorageDirectory的集合中挑选一个最新版本的edits文件,当然IMAGE_AND_EDITS类型的StorageDirectory既可以看作是IMAGE类型的,也可以看作是EDITS类型的。至于如何判断一个StorageDirectory是不是最新的,主要是根据StorageDirectory中fstime文件存放的时间戳来确定的。在选取最新的fsimage、edits文件之后还要保证他们的版本相同,否则整个NameNode节点的启动过程失败。下面还是用一张图来描述整个过程吧!


     在理想的情况下,上述过程没有半点问题,但是如果在这一过程中突然出现了宕机或者是断电的异常情况而被迫中断,特别是在保存最新的目录树的时候,那么NameNode在重启之后又是如何恢复的呢?实际上,在上面的流程图中,我故意遗漏了一个相当重要的过程——checkpoint的恢复过程。这个过程在加载当前最新的fsimage文件之前,主要是恢复最新的fsimage、edits目录。


         对于上面的这个简单的恢复过程,在edits.new文件存在的情况下继续完成上一次的加载,而在 edits.new文件不存在的情况下放弃上一次的加载,回到上一次加载之前的状态,这是为什么呢?我会在后面的博文中提及。


你可能感兴趣的:(Hadoop)