hbase源码系列(六)HMaster启动过程

这一章是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的启动过程做的工作基本结束了。

时间: 2024-09-09 02:21:42

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

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

先上一张图讲一下Compaction和Split的关系,这样会比较直观一些. Compaction把多个MemStore flush出来的StoreFile合并成一个文件,而Split则是把过大的文件Split成两个. 之前在Delete的时候,我们知道它其实并没有真正删除数据的,那总不能一直不删吧,下面我们就介绍一下它删除数据的过程,它就是Compaction. 在讲源码之前,先说一下它的分类和作用. Compaction主要起到如下几个作用: 1)合并文件 2)清除删除.过期.多余版本的数据

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

继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以走这个过程,所以就一起写了. Get 我们打开HRegionServer找到get方法.Get的方法处理分两种,设置了ClosestRowBefore和没有设置的,一般来讲,我们都是知道了明确的rowkey,不太会设置这个参数,它默认是false的. if (get.hasClosestRowBef

HBase源码分析:HTable put过程

HBase版本:0.94.15-cdh4.7.0 在 HBase中,大部分的操作都是在RegionServer完成的,Client端想要插入.删除.查询数据都需要先找到相应的 RegionServer.什么叫相应的RegionServer?就是管理你要操作的那个Region的RegionServer.Client本身并 不知道哪个RegionServer管理哪个Region,那么它是如何找到相应的RegionServer的?本文就是在研究源码的基础上了解这个过程. 首先来看看写过程的序列图: 客

hbase源码系列(十五)终结篇&amp;Scan续集--&gt;如何查询出来下一个KeyValue

这是这个系列的最后一篇了,实在没精力写了,本来还想写一下hbck的,这个东西很常用,当hbase的Meta表出现错误的时候,它能够帮助我们进行修复,无奈看到3000多行的代码时,退却了,原谅我这点自私的想法吧. 在讲<Get.Scan在服务端是如何处理?>当中的nextInternal流程,它的第一步从storeHeap当中取出当前kv,这块其实有点儿小复杂的,因为它存在异构的Scanner(一个MemStoreScanner和多个StoreFileScanner),那怎么保证从storeHe

hbase源码系列(八)从Snapshot恢复表

在看这一章之前,建议大家先去看一下snapshot的使用.这一章是上一章snapshot的续集,上一章了讲了怎么做snapshot的原理,这一章就怎么从snapshot恢复表. restoreSnapshot方法位于HMaster当中,这个方法没几行代码,调用了SnapshotManager的restoreSnapshot方法. // 检查meta表当中是否存在该表 if (MetaReader.tableExists(master.getCatalogTracker(), tableName)

hbase源码系列(二)HTable 探秘

hbase的源码终于搞一个段落了,在接下来的一个月,着重于把看过的源码提炼一下,对一些有意思的主题进行分享一下.继上一篇讲了负载均衡之后,这一篇我们从client开始讲吧,从client到master再到region server,按照这个顺序来开展,网友也可以对自己感兴趣的部分给我留言或者直接联系我的QQ. 现在我们讲一下HTable吧,为什么讲HTable,因为这是我们最常见的一个类,这是我们对hbase中数据的操作的入口. 1.Put操作 下面是一个很简单往hbase插入一条记录的例子.

hbase源码系列(七)Snapshot的过程

在看这一章之前,建议大家先去看一下snapshot的使用.可能有人会有疑问为什么要做Snapshot,hdfs不是自带了3个备份吗,这是个很大的误区,要知道hdfs的3个备份是用于防止网络传输中的失败或者别的异常情况导致数据块丢失或者不正确,它不能避免人为的删除数据导致的后果.它就想是给数据库做备份,尤其是做删除动作之前,不管是hbase还是hdfs,请经常做Snapshot,否则哪天手贱了... 直接进入主题吧,上代码. public void takeSnapshot(SnapshotDes

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

在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTable探秘>的朋友都会有印象,没看过的建议回去先看看,Put是通过MultiServerCallable来提交的多个Put,好,我们就先去这个类吧,在call方法里面,我们找到了这句. responseProto = getStub().multi(controller, requestProto); 它调用了Region Server的multi方法.好,我们立即杀到HReg

hbase源码系列(三)Client如何找到正确的Region Server

客户端在进行put.delete.get等操作的时候,它都需要数据到底存在哪个Region Server上面,这个定位的操作是通过HConnection.locateRegion方法来完成的. loc = hConnection.locateRegion(this.tableName, row.getRow()); 这里我们首先要讲hbase的两张元数据表-ROOT-和.META.表,它们一个保存着region的分部信息,一个保存着region的详细信息.在<hbase实战>这本书里面详细写了