分享

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

本帖最后由 坎蒂丝_Swan 于 2014-12-29 12:59 编辑

问题导读
1.阻塞变成ActiveMaster的过程是怎样的?
2.region的分配工作是如何进行的?






这一章是server端开始的第一章,有兴趣的朋友先去看一下hbase的架构图。

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

  1. * <li>阻塞直到变成ActiveMaster
  2. * <li>结束初始化操作
  3. * <li>循环
  4. * <li>停止服务并执行清理操作* </ol>
复制代码


HMaster是没有单点问题是,因为它可以同时启动多个HMaster,然后通过zk的选举算法选出一个HMaster来。
  我们首先来看看这个阻塞直到变成ActiveMaster的过程吧。
  1、如果不是master的话,就一直睡,isActiveMaster的判断条件是,在zk当中没有master节点,如果没有就一直等待。master节点建立之后,就开始向Master冲刺了,先到先得。
  2、尝试着在master节点下面把自己的ServerName给加上去,如果加上去了,它就成为master了,成为master之后,就把自己从备份节点当中删除。
  3、如果没有成为master,把自己添加到备份节点,同时检查一下当前的master节点,如果是和自己一样,那就是出现异常了,明明是设置成功了,确说不成功,接下来它就会一直等待,等到master死掉。

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

  1. /** 专门负责master和hdfs交互的类  */
  2.   private MasterFileSystem fileSystemManager;
  3.   /** 专门用于管理Region Server的管理器  */
  4.   ServerManager serverManager;
  5.   /** 专门用于管理zk当中nodes的节点  */
  6.   AssignmentManager assignmentManager;
  7.   /** 负责负载均衡的类  */
  8.   private LoadBalancer balancer;
  9.   /** 负责读取在hdfs上面的表定义的类 */
  10.   private TableDescriptors tableDescriptors;
  11.   /** 表级别的分布式锁,专门负责监控模式的变化  */
  12.   private TableLockManager tableLockManager;
  13.    /** 负责监控表的备份 */
  14.   private SnapshotManager snapshotManager;
复制代码

这些类都会被实例化,具体的顺序就不讲了,这个不是特别重要。开学啦,等到region server过来报道,还要记录一下在zk当中注册了的,但是没有在master这里报道的,不做处理。
  1. // 等待region server过来注册,至少要有一个启动了
  2.     this.serverManager.waitForRegionServers(status);
  3.     // 检查在zk当中注册了,但是没在master这里注册的server
  4.     for (ServerName sn: this.regionServerTracker.getOnlineServers()) {
  5.       if (!this.serverManager.isServerOnline(sn)
  6.           && serverManager.checkAlreadySameHostPortAndRecordNewServer(
  7.               sn, ServerLoad.EMPTY_SERVERLOAD)) {
  8.         LOG.info("Registered server found up in zk but who has not yet "
  9.           + "reported in: " + sn);
  10.       }
  11.     }
复制代码
分配META表前的准备工作,Split Meta表的日志
okay,下面是重头戏了,准备分配meta表了,先启动个计时器。

  1. // 启动超时检查器了哦
  2. if (!masterRecovery) {
  3.     this.assignmentManager.startTimeOutMonitor();
  4. }
复制代码

上代码,从日志文件里面找出来挂了的server,然后对这些server做处理。
  1. // 从WALs目录下找出挂了的机器
  2.     Set<ServerName> previouslyFailedServers = this.fileSystemManager
  3.         .getFailedServersFromLogFolders();
  4.     // 删除之前运行的时候正在恢复的region,在zk的recovering-regions下所有的region节点一个不留
  5.     this.fileSystemManager.removeStaleRecoveringRegionsFromZK(previouslyFailedServers);
  6.     // 获取就的meta表的位置,如果在已经挂了的机器上
  7.     ServerName oldMetaServerLocation = this.catalogTracker.getMetaLocation();
  8.     //如果meta表在之前挂了的server上面,就需要把meta表的日志从日志文件里面单独拿出来
  9.     if (oldMetaServerLocation != null && previouslyFailedServers.contains(oldMetaServerLocation)) {
  10.       splitMetaLogBeforeAssignment(oldMetaServerLocation);
  11.     }
复制代码

F3进入getFailedServersFromLogFolders方法。
  1. //遍历WALs下面的文件
  2.         FileStatus[] logFolders = FSUtils.listStatus(this.fs, logsDirPath, null);//获取在线的server的集合
  3.         Set<ServerName> onlineServers = ((HMaster) master).getServerManager().getOnlineServers().keySet();
  4.         for (FileStatus status : logFolders) {
  5.           String sn = status.getPath().getName();
  6.           //如果目录名里面包含-splitting,就是正在split的日志
  7.           if (sn.endsWith(HLog.SPLITTING_EXT)) {
  8.             sn = sn.substring(0, sn.length() - HLog.SPLITTING_EXT.length());
  9.           }
  10.           //把字符串的机器名转换成ServerName
  11.           ServerName serverName = ServerName.parseServerName(sn);
  12.           //如果在线的机器里面不包括这个ServerName就认为它是挂了
  13.           if (!onlineServers.contains(serverName)) {
  14.             serverNames.add(serverName);
  15.           } else {
  16.             LOG.info("Log folder " + status.getPath() + " belongs to an existing region server");
  17.           }
  18.         }
复制代码

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

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

  1. private void splitMetaLogBeforeAssignment(ServerName currentMetaServer) throws IOException {
  2.     //该参数默认为false
  3.     if (this.distributedLogReplay) {
  4.       // In log replay mode, we mark hbase:meta region as recovering in ZK
  5.       Set<HRegionInfo> regions = new HashSet<HRegionInfo>();
  6.       regions.add(HRegionInfo.FIRST_META_REGIONINFO);
  7.       this.fileSystemManager.prepareLogReplay(currentMetaServer, regions);
  8.     } else {
  9.       // In recovered.edits mode: create recovered edits file for hbase:meta server
  10.       this.fileSystemManager.splitMetaLog(currentMetaServer);
  11.     }
  12.   }
复制代码


可以看出来这里面有两种模式,分布式文件恢复模式,通过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结尾。


  1. public void splitLog(final Set<ServerName> serverNames, PathFilter filter) throws IOException {
  2.     long splitTime = 0, splitLogSize = 0;
  3.     //修改WALs日志目录的名称,在需要分裂的目录的名称后面加上.splitting,准备分裂
  4.     List<Path> logDirs = getLogDirs(serverNames);
  5.     //把这些挂了的server记录到splitLogManager的deadWorkers的列表
  6.     splitLogManager.handleDeadWorkers(serverNames);
  7.     splitTime = EnvironmentEdgeManager.currentTimeMillis();
  8.     //split日志
  9.     splitLogSize = splitLogManager.splitLogDistributed(serverNames, logDirs, filter);
  10.     splitTime = EnvironmentEdgeManager.currentTimeMillis() - splitTime;
  11.     //记录split结果到统计数据当中
  12.     if (this.metricsMasterFilesystem != null) {
  13.       if (filter == META_FILTER) {
  14.         this.metricsMasterFilesystem.addMetaWALSplit(splitTime, splitLogSize);
  15.       } else {
  16.         this.metricsMasterFilesystem.addSplit(splitTime, splitLogSize);
  17.       }
  18.     }
  19.   }
复制代码

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

  1. public long splitLogDistributed(final Set<ServerName> serverNames, final List<Path> logDirs,
  2.       PathFilter filter) throws IOException {//读取文件
  3.     FileStatus[] logfiles = getFileList(logDirs, filter);long totalSize = 0;
  4.     //任务跟踪器,一个batch包括N个任务,最后统计batch当中的总数
  5.     TaskBatch batch = new TaskBatch();
  6.     Boolean isMetaRecovery = (filter == null) ? null : false;
  7.     for (FileStatus lf : logfiles) {
  8.       totalSize += lf.getLen();
  9.       //获得root后面的相对路径
  10.       String pathToLog = FSUtils.removeRootPath(lf.getPath(), conf);
  11.       //把任务插入到Split任务列表当中
  12.       if (!enqueueSplitTask(pathToLog, batch)) {
  13.         throw new IOException("duplicate log split scheduled for " + lf.getPath());
  14.       }
  15.     }
  16.     //等待任务结束
  17.     waitForSplittingCompletion(batch, status);
  18.     if (filter == MasterFileSystem.META_FILTER)
  19.       isMetaRecovery = true;
  20.     }
  21.     //清理recovering的状态,否则region server不让访问正在恢复当中的region
  22.     this.removeRecoveringRegionsFromZK(serverNames, isMetaRecovery);
  23.     if (batch.done != batch.installed) {
  24.       //启动的任务和完成的任务不相等
  25.       batch.isDead = true;
  26.       String msg = "error or interrupted while splitting logs in "
  27.         + logDirs + " Task = " + batch;
  28.       throw new IOException(msg);
  29.     }
  30.     //最后清理日志
  31.     for(Path logDir: logDirs){try {
  32.         if (fs.exists(logDir) && !fs.delete(logDir, false)) {
  33.         }
  34.       } catch (IOException ioe) {
  35.       }
  36.     }
  37.     status.markComplete(msg);
  38.     return totalSize;
  39.   }
复制代码


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

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

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

  1. void assignMeta(MonitoredTask status)
  2.       throws InterruptedException, IOException, KeeperException {
  3.     // Work on meta region
  4.     int assigned = 0;
  5.     ServerName logReplayFailedMetaServer = null;
  6.     //在RegionStates里面状态状态,表名该region正在变化当中
  7.     assignmentManager.getRegionStates().createRegionState(HRegionInfo.FIRST_META_REGIONINFO);
  8.     //处理meta表第一个region,重新指派
  9.     boolean rit = this.assignmentManager.processRegionInTransitionAndBlockUntilAssigned(HRegionInfo.FIRST_META_REGIONINFO);
  10.     //这个应该是meta表,hbase:meta,等待它在zk当中可以被访问
  11.     boolean metaRegionLocation = this.catalogTracker.verifyMetaRegionLocation(timeout);
  12.     if (!metaRegionLocation) {
  13.       assigned++;
  14.       if (!rit) {
  15.         // 没分配成功,又得回头再做一遍准备工作
  16.         ServerName currentMetaServer = this.catalogTracker.getMetaLocation();
  17.         if (currentMetaServer != null) {
  18.           if (expireIfOnline(currentMetaServer)) {
  19.             splitMetaLogBeforeAssignment(currentMetaServer);
  20.             if (this.distributedLogReplay) {
  21.               logReplayFailedMetaServer = currentMetaServer;
  22.             }
  23.           }
  24.         }
  25.         //删掉zk当中的meta表的位置,再分配
  26.         assignmentManager.assignMeta();
  27.       }
  28.     } else {
  29.       //指派了,就更新一下它的状态为online
  30.       this.assignmentManager.regionOnline(HRegionInfo.FIRST_META_REGIONINFO,this.catalogTracker.getMetaLocation());
  31.     }
  32.     //在zk当中启用meta表
  33.     enableMeta(TableName.META_TABLE_NAME);
  34.     // 启动关机处理线程
  35.     enableServerShutdownHandler(assigned != 0);
  36.     if (logReplayFailedMetaServer != null) {
  37.       // 这里不是再来一次,注意了啊,这个是分布式模式状态下要进行的一次meta表的日志split,
  38.       //回头看一下这个变量啥时候赋值就知道了
  39.       this.fileSystemManager.splitMetaLog(logReplayFailedMetaServer);
  40.     }
  41.   }
复制代码

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

  1. public RegionOpeningState sendRegionOpen(final ServerName server,
  2.       HRegionInfo region, int versionOfOfflineNode, List<ServerName> favoredNodes)
  3.               throws IOException {
  4.     AdminService.BlockingInterface admin = getRsAdmin(server);
  5.     if (admin == null) {return RegionOpeningState.FAILED_OPENING;
  6.     }
  7.     //构建openRegion请求,
  8.     OpenRegionRequest request =
  9.       RequestConverter.buildOpenRegionRequest(region, versionOfOfflineNode, favoredNodes);
  10.     try {
  11.       //调用指定的Region Server的openRegion方法
  12.       OpenRegionResponse response = admin.openRegion(null, request);
  13.       return ResponseConverter.getRegionOpeningState(response);
  14.     } catch (ServiceException se) {
  15.       throw ProtobufUtil.getRemoteException(se);
  16.     }
  17.   }
复制代码

这个工作完成, 如果是分布式文件恢复模式,还需要进行这个工作,recovered.edit模式之前已经干过了
  1. //获取正在恢复的meta region server
  2. Set<ServerName> previouslyFailedMetaRSs = getPreviouselyFailedMetaServersFromZK();
  3. if (this.distributedLogReplay && (!previouslyFailedMetaRSs.isEmpty())) {
  4.       previouslyFailedMetaRSs.addAll(previouslyFailedServers);
  5.       this.fileSystemManager.splitMetaLog(previouslyFailedMetaRSs);
  6. }
复制代码

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

  1. // 已经恢复了meta表,我们现在要处理掉其它失败的server
  2.     for (ServerName tmpServer : previouslyFailedServers) {
  3.       this.serverManager.processDeadServer(tmpServer, true);
  4.     }
  5.     // 如果是failover的情况,就修复assignmentManager当中有问题的region状态,如果是新启动的,就分配所有的用户region
  6.     this.assignmentManager.joinCluster();
复制代码


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

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


hbase源码系列(二)HTable 探秘hbase源码系列(三)Client如何找到正确的Region Server
hbase源码系列(四)数据模型-表定义和列族定义的具体含义
hbase源码系列(五)Trie单词查找树
hbase源码系列(六)HMaster启动过程
hbase源码系列(七)Snapshot的过程
hbase源码系列(八)从Snapshot恢复表
hbase源码系列(九)StoreFile存储格式
hbase源码系列(十)HLog与日志恢复
hbase源码系列(十一)Put、Delete在服务端是如何处理?
hbase源码系列(十二)Get、Scan在服务端是如何处理?
hbase源码系列(十三)缓存机制MemStore与Block Cache
hbase源码之如何查询出来下一个KeyValue
hbase源码Compact和Split

欢迎加入about云群90371779322273151432264021 ,云计算爱好者群,亦可关注about云腾讯认证空间||关注本站微信

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条