这一章是server端开始的第一章,有兴趣的朋友先去看一下hbase的架构图,我专门从网上弄下来的。

  按照HMaster的run方法的注释,我们可以了解到它的启动过程会去做以下的动作。

 * <li>阻塞直到变成ActiveMaster
 * <li>结束初始化操作
 * <li>循环
 * <li>停止服务并执行清理操作* </ol>

  HMaster是没有单点问题是,因为它可以同时启动多个HMaster,然后通过zk的选举算法选出一个HMaster来。

  我们首先来看看这个阻塞直到变成ActiveMaster的过程吧。

  1、如果不是master的话,就一直睡,isActiveMaster的判断条件是,在zk当中没有master节点,如果没有就一直等待。master节点建立之后,就开始向Master冲刺了,先到先得。

  2、尝试着在master节点下面把自己的ServerName给加上去,如果加上去了,它就成为master了,成为master之后,就把自己从备份节点当中删除。

  3、如果没有成为master,把自己添加到备份节点,同时检查一下当前的master节点,如果是和自己一样,那就是出现异常了,明明是设置成功了,确说不成功,接下来它就会一直等待,等到master死掉。

  成为master之后的结束初始化操作,这才是重头戏啊,前面的都是小意思,实例化的代码我就补贴了,看着也没啥意思,就把这些属性贴出来吧,让大家认识认识。

 /** 专门负责master和hdfs交互的类  */
  private MasterFileSystem fileSystemManager;

  /** 专门用于管理Region Server的管理器  */
  ServerManager serverManager;

  /** 专门用于管理zk当中nodes的节点  */
  AssignmentManager assignmentManager;

  /** 负责负载均衡的类  */
  private LoadBalancer balancer;

  /** 负责读取在hdfs上面的表定义的类 */
  private TableDescriptors tableDescriptors;

  /** 表级别的分布式锁,专门负责监控模式的变化  */
  private TableLockManager tableLockManager;

   /** 负责监控表的备份 */
  private SnapshotManager snapshotManager;

  这些类都会被实例化,具体的顺序就不讲了,这个不是特别重要。开学啦,等到region server过来报道,还要记录一下在zk当中注册了的,但是没有在master这里报道的,不做处理。

    // 等待region server过来注册,至少要有一个启动了
    this.serverManager.waitForRegionServers(status);
    // 检查在zk当中注册了,但是没在master这里注册的server
    for (ServerName sn: this.regionServerTracker.getOnlineServers()) {
      if (!this.serverManager.isServerOnline(sn)
          && serverManager.checkAlreadySameHostPortAndRecordNewServer(
              sn, ServerLoad.EMPTY_SERVERLOAD)) {
        LOG.info("Registered server found up in zk but who has not yet "
          + "reported in: " + sn);
      }
    }

分配META表前的准备工作,Split Meta表的日志

  okay,下面是重头戏了,准备分配meta表了,先启动个计时器。

// 启动超时检查器了哦
if (!masterRecovery) {
    this.assignmentManager.startTimeOutMonitor();
}

  上代码,从日志文件里面找出来挂了的server,然后对这些server做处理。

  // 从WALs目录下找出挂了的机器
    Set<ServerName> previouslyFailedServers = this.fileSystemManager
        .getFailedServersFromLogFolders();
    // 删除之前运行的时候正在恢复的region,在zk的recovering-regions下所有的region节点一个不留
    this.fileSystemManager.removeStaleRecoveringRegionsFromZK(previouslyFailedServers);

    // 获取就的meta表的位置,如果在已经挂了的机器上
    ServerName oldMetaServerLocation = this.catalogTracker.getMetaLocation();
    //如果meta表在之前挂了的server上面,就需要把meta表的日志从日志文件里面单独拿出来
    if (oldMetaServerLocation != null && previouslyFailedServers.contains(oldMetaServerLocation)) {
      splitMetaLogBeforeAssignment(oldMetaServerLocation);
    }

  F3进入getFailedServersFromLogFolders方法。

     //遍历WALs下面的文件
        FileStatus[] logFolders = FSUtils.listStatus(this.fs, logsDirPath, null);//获取在线的server的集合
        Set<ServerName> onlineServers = ((HMaster) master).getServerManager().getOnlineServers().keySet();
        for (FileStatus status : logFolders) {
          String sn = status.getPath().getName();          //如果目录名里面包含-splitting,就是正在split的日志
          if (sn.endsWith(HLog.SPLITTING_EXT)) {
            sn = sn.substring(0, sn.length() - HLog.SPLITTING_EXT.length());
          }
          //把字符串的机器名转换成ServerName
          ServerName serverName = ServerName.parseServerName(sn);
          //如果在线的机器里面不包括这个ServerName就认为它是挂了
          if (!onlineServers.contains(serverName)) {
            serverNames.add(serverName);
          } else {
            LOG.info("Log folder " + status.getPath() + " belongs to an existing region server");
          }
        }

  从代码上面看得出来,从WALs日志的目录下面找,目录名称里面就包括ServerName,取出来和在线的Server对比一下,把不在线的加到集合里面,最后返回。看来这个目录下都是出了问题的Server才会在这里混。

  我们接着回到上面的逻辑,查出来失败的Server之后,从zk当中把之前的Meta表所在的位置取出来,如果Meta表在这些挂了的Server里面,就糟糕了。。得启动恢复措施了。。。先把META表的日志从日志堆里找出来。我们进入splitMetaLogBeforeAssignment这个方法里面看看吧。

 private void splitMetaLogBeforeAssignment(ServerName currentMetaServer) throws IOException {
    //该参数默认为false
    if (this.distributedLogReplay) {
      // In log replay mode, we mark hbase:meta region as recovering in ZK
      Set<HRegionInfo> regions = new HashSet<HRegionInfo>();
      regions.add(HRegionInfo.FIRST_META_REGIONINFO);
      this.fileSystemManager.prepareLogReplay(currentMetaServer, regions);
    } else {
      // In recovered.edits mode: create recovered edits file for hbase:meta server
      this.fileSystemManager.splitMetaLog(currentMetaServer);
    }
  }

  可以看出来这里面有两种模式,分布式文件恢复模式,通过zk来恢复,还有一种是recovered.edit模式,通过创建recovered.edits文件来恢复。文件恢复是通过hbase.master.distributed.log.replay参数来设置,默认是false,走的recovered.edit模式。看得出来,这个函数是为恢复做准备工作的,如果是分布式模式,就执行prepareLogReplay准备日志恢复,否则就开始创建recovered.edits恢复文件。

  (a)prepareLogReplay方法当中,把HRegionInfo.FIRST_META_REGIONINFO这个region添加到了recovering-regions下面,置为恢复中的状态。

  (b)下面看看splitMetaLog吧,它是通过调用这个方法来执行split日志的,通过filter来过滤META或者非META表的日志,META表的日志以.meta结尾。

public void splitLog(final Set<ServerName> serverNames, PathFilter filter) throws IOException {
    long splitTime = 0, splitLogSize = 0;
    //修改WALs日志目录的名称,在需要分裂的目录的名称后面加上.splitting,准备分裂
    List<Path> logDirs = getLogDirs(serverNames);
    //把这些挂了的server记录到splitLogManager的deadWorkers的列表
    splitLogManager.handleDeadWorkers(serverNames);
    splitTime = EnvironmentEdgeManager.currentTimeMillis();
    //split日志
    splitLogSize = splitLogManager.splitLogDistributed(serverNames, logDirs, filter);
    splitTime = EnvironmentEdgeManager.currentTimeMillis() - splitTime;
    //记录split结果到统计数据当中
    if (this.metricsMasterFilesystem != null) {
      if (filter == META_FILTER) {
        this.metricsMasterFilesystem.addMetaWALSplit(splitTime, splitLogSize);
      } else {
        this.metricsMasterFilesystem.addSplit(splitTime, splitLogSize);
      }
    }
  }

  上面也带了不少注释了,不废话了,进splitLogDistributed里面瞅瞅吧。

public long splitLogDistributed(final Set<ServerName> serverNames, final List<Path> logDirs,
      PathFilter filter) throws IOException {//读取文件
    FileStatus[] logfiles = getFileList(logDirs, filter);long totalSize = 0;
    //任务跟踪器,一个batch包括N个任务,最后统计batch当中的总数
    TaskBatch batch = new TaskBatch();
    Boolean isMetaRecovery = (filter == null) ? null : false;
    for (FileStatus lf : logfiles) {
      totalSize += lf.getLen();
      //获得root后面的相对路径
      String pathToLog = FSUtils.removeRootPath(lf.getPath(), conf);
      //把任务插入到Split任务列表当中
      if (!enqueueSplitTask(pathToLog, batch)) {
        throw new IOException("duplicate log split scheduled for " + lf.getPath());
      }
    }
    //等待任务结束
    waitForSplittingCompletion(batch, status);
    if (filter == MasterFileSystem.META_FILTER)
      isMetaRecovery = true;
    }
    //清理recovering的状态,否则region server不让访问正在恢复当中的region
    this.removeRecoveringRegionsFromZK(serverNames, isMetaRecovery);

    if (batch.done != batch.installed) {
      //启动的任务和完成的任务不相等
      batch.isDead = true;
      String msg = "error or interrupted while splitting logs in "
        + logDirs + " Task = " + batch;
      throw new IOException(msg);
    }
    //最后清理日志
    for(Path logDir: logDirs){try {
        if (fs.exists(logDir) && !fs.delete(logDir, false)) {
        }
      } catch (IOException ioe) {
      }
    }
    status.markComplete(msg);
    return totalSize;
  }

  它的所有的文件的split文件都插入到一个split队列里面去,然后等待结束,这里面有点儿绕了,它是到zk的splitWALs节点下面为这个文件创建一个节点,不是原来的相对路径名,是URLEncoder.encode(s, "UTF-8")加密过的。

  呵呵,看到这里是不是要晕了呢,它是在zk里面创建一个节点,然后不干活,当然不是啦,在每个Region Server里面读会启动一个SplitLogWorker去负责处理这下面的日志。split处理过程在HLogSplitter.splitLogFile方法里面,具体不讲了,它会把恢复文件在region下面生成一个recovered.edits目录里面。

  

分配META表

  下面就开始指派Meta表的region啦。

void assignMeta(MonitoredTask status)
      throws InterruptedException, IOException, KeeperException {
    // Work on meta region
    int assigned = 0;
    ServerName logReplayFailedMetaServer = null;
    //在RegionStates里面状态状态,表名该region正在变化当中
    assignmentManager.getRegionStates().createRegionState(HRegionInfo.FIRST_META_REGIONINFO);
    //处理meta表第一个region,重新指派
    boolean rit = this.assignmentManager.processRegionInTransitionAndBlockUntilAssigned(HRegionInfo.FIRST_META_REGIONINFO);
    //这个应该是meta表,hbase:meta,等待它在zk当中可以被访问
    boolean metaRegionLocation = this.catalogTracker.verifyMetaRegionLocation(timeout);
    if (!metaRegionLocation) {
      assigned++;
      if (!rit) {
        // 没分配成功,又得回头再做一遍准备工作
        ServerName currentMetaServer = this.catalogTracker.getMetaLocation();
        if (currentMetaServer != null) {
          if (expireIfOnline(currentMetaServer)) {
            splitMetaLogBeforeAssignment(currentMetaServer);
            if (this.distributedLogReplay) {
              logReplayFailedMetaServer = currentMetaServer;
            }
          }
        }
        //删掉zk当中的meta表的位置,再分配
        assignmentManager.assignMeta();
      }
    } else {
      //指派了,就更新一下它的状态为online
      this.assignmentManager.regionOnline(HRegionInfo.FIRST_META_REGIONINFO,this.catalogTracker.getMetaLocation());
    }
    //在zk当中启用meta表
    enableMeta(TableName.META_TABLE_NAME);

    // 启动关机处理线程
    enableServerShutdownHandler(assigned != 0);

    if (logReplayFailedMetaServer != null) {
      // 这里不是再来一次,注意了啊,这个是分布式模式状态下要进行的一次meta表的日志split,      //回头看一下这个变量啥时候赋值就知道了
      this.fileSystemManager.splitMetaLog(logReplayFailedMetaServer);
    }
  }

  历经千辛万苦跟踪到了这个方法里面,通过RPC,向随机抽出来的Region Server发送请求,让它打开region。

public RegionOpeningState sendRegionOpen(final ServerName server,
      HRegionInfo region, int versionOfOfflineNode, List<ServerName> favoredNodes)
              throws IOException {
    AdminService.BlockingInterface admin = getRsAdmin(server);
    if (admin == null) {return RegionOpeningState.FAILED_OPENING;
    }
    //构建openRegion请求,
    OpenRegionRequest request =
      RequestConverter.buildOpenRegionRequest(region, versionOfOfflineNode, favoredNodes);
    try {
      //调用指定的Region Server的openRegion方法
      OpenRegionResponse response = admin.openRegion(null, request);
      return ResponseConverter.getRegionOpeningState(response);
    } catch (ServiceException se) {
      throw ProtobufUtil.getRemoteException(se);
    }
  }

  这个工作完成, 如果是分布式文件恢复模式,还需要进行这个工作,recovered.edit模式之前已经干过了

//获取正在恢复的meta region server
Set<ServerName> previouslyFailedMetaRSs = getPreviouselyFailedMetaServersFromZK();
if (this.distributedLogReplay && (!previouslyFailedMetaRSs.isEmpty())) {
      previouslyFailedMetaRSs.addAll(previouslyFailedServers);
      this.fileSystemManager.splitMetaLog(previouslyFailedMetaRSs);
}

  

分配用户Region

  之后就是一些清理工作了,处理掉失败的server,修改一些不正确的region的状态,分配所有用户的region。

    // 已经恢复了meta表,我们现在要处理掉其它失败的server
    for (ServerName tmpServer : previouslyFailedServers) {
      this.serverManager.processDeadServer(tmpServer, true);
    }

    // 如果是failover的情况,就修复assignmentManager当中有问题的region状态,如果是新启动的,就分配所有的用户region
    this.assignmentManager.joinCluster();

  

  分配region的工作都是由assignmentManager来完成的,在joinCluster方法中的调用的processDeadServersAndRegionsInTransition的最后一句调用的assignAllUserRegions方法,隐藏得很深。。经过分配过的region,hmaster在启动的时候默认会沿用上一次的结果,就不再变动了,这个是由一个参数来维护的hbase.master.startup.retainassign,默认是true。分配用户region的方法和分配meta表的过程基本是一致的。

  至此HMaster的启动过程做的工作基本结束了。

hbase源码系列(六)HMaster启动过程的更多相关文章

  1. Spring源码系列——容器的启动过程(一)

    一. 前言 Spring家族特别庞大,对于开发人员而言,要想全面征服Spring家族,得花费不少的力气.俗话说,打蛇打七寸,那么Spring家族的"七寸"是什么呢?我心目中的答案一 ...

  2. 10 hbase源码系列(十)HLog与日志恢复

    hbase源码系列(十)HLog与日志恢复   HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢 ...

  3. 11 hbase源码系列(十一)Put、Delete在服务端是如何处理

    hbase源码系列(十一)Put.Delete在服务端是如何处理?    在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTab ...

  4. hbase源码系列(十二)Get、Scan在服务端是如何处理

    hbase源码系列(十二)Get.Scan在服务端是如何处理?   继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...

  5. 9 hbase源码系列(九)StoreFile存储格式

    hbase源码系列(九)StoreFile存储格式    从这一章开始要讲Region Server这块的了,但是在讲Region Server这块之前得讲一下StoreFile,否则后面的不好讲下去 ...

  6. HBase源码系列之HFile

    本文讨论0.98版本的hbase里v2版本.其实对于HFile能有一个大体的较深入理解是在我去查看"到底是不是一条记录不能垮block"的时候突然意识到的. 首先说一个对HFile ...

  7. hbase源码系列(十四)Compact和Split

    先上一张图讲一下Compaction和Split的关系,这样会比较直观一些. Compaction把多个MemStore flush出来的StoreFile合并成一个文件,而Split则是把过大的文件 ...

  8. hbase源码系列(十二)Get、Scan在服务端是如何处理?

    继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...

  9. hbase源码系列(十)HLog与日志恢复

    HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复,下面先看看HLog的图. 旧版的HLog是实 ...

随机推荐

  1. 【转载并整理】javaweb单点登录

    很好的一篇,讲述原理的文章: http://www.importnew.com/22863.html https://www.cnblogs.com/Leo_wl/p/6111623.html 京东s ...

  2. XGB 调参基本方法

    - xgboost 基本方法和默认参数 - 实战经验中调参方法 - 基于实例具体分析 在训练过程中主要用到两个方法:xgboost.train()和xgboost.cv(). xgboost.trai ...

  3. 更改MySQL数据库目录位置[zz]

    MYSQL默认的数据文件存储目录为/var/lib/mysql.假如要把目录移到/home/data下需要进行下面几步:1.home目录下建立data目录cd /homemkdir data2.把My ...

  4. [CTCI] 最大子方阵

    最大子方阵 题目描述 有一个方阵,其中每个单元(像素)非黑即白(非0即1),请设计一个高效算法,找到四条边颜色相同的最大子方阵. 给定一个01方阵mat,同时给定方阵的边长n,请返回最大子方阵的边长. ...

  5. Recyclerview 出现 java.lang.IndexOutOfBoundsException: Inconsistency detected 异常

    使用 RecyclerView 的时候报错 java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view hold ...

  6. 什么是内联函数(inline function)

    In C, we have used Macro function an optimized technique used by compiler to reduce the execution ti ...

  7. android studio(AS) Duplicate files copied in APK META-INF/NOTICE.txt

    File 1: /home/slava/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpmime/4.3.1/f789 ...

  8. ios--网页js调用oc代码+传递参数+避免中文参数乱码的解决方案(实例)

    此解决方案原理: 1.在ViewController.h中声明方法和成员变量,以及webView的委托: // //  ViewController.h //  JS_IOS_01 // //  Cr ...

  9. sqlite第三方类库:FMDB使用

    转自:http://www.cnblogs.com/wuhenke/archive/2012/02/07/2341656.html 本文转自一位台湾ios开发者的blog,由于blog地址被墙掉,转发 ...

  10. 菜鸟学SSH(八)——Hibernate对象的三种状态

    前面写了几篇关于SSH的博客,但不是Struts就是Spring,Hibernate还从来没写过呢.说好是SSH的,怎么可以光写那两个,而不写Hibernate呢对吧.今天就先说说Hibernate对 ...