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的启动过程做的工作基本结束了。
hbase源码系列(六)HMaster启动过程的更多相关文章
- Spring源码系列——容器的启动过程(一)
一. 前言 Spring家族特别庞大,对于开发人员而言,要想全面征服Spring家族,得花费不少的力气.俗话说,打蛇打七寸,那么Spring家族的"七寸"是什么呢?我心目中的答案一 ...
- 10 hbase源码系列(十)HLog与日志恢复
hbase源码系列(十)HLog与日志恢复 HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢 ...
- 11 hbase源码系列(十一)Put、Delete在服务端是如何处理
hbase源码系列(十一)Put.Delete在服务端是如何处理? 在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTab ...
- hbase源码系列(十二)Get、Scan在服务端是如何处理
hbase源码系列(十二)Get.Scan在服务端是如何处理? 继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...
- 9 hbase源码系列(九)StoreFile存储格式
hbase源码系列(九)StoreFile存储格式 从这一章开始要讲Region Server这块的了,但是在讲Region Server这块之前得讲一下StoreFile,否则后面的不好讲下去 ...
- HBase源码系列之HFile
本文讨论0.98版本的hbase里v2版本.其实对于HFile能有一个大体的较深入理解是在我去查看"到底是不是一条记录不能垮block"的时候突然意识到的. 首先说一个对HFile ...
- hbase源码系列(十四)Compact和Split
先上一张图讲一下Compaction和Split的关系,这样会比较直观一些. Compaction把多个MemStore flush出来的StoreFile合并成一个文件,而Split则是把过大的文件 ...
- hbase源码系列(十二)Get、Scan在服务端是如何处理?
继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...
- hbase源码系列(十)HLog与日志恢复
HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复,下面先看看HLog的图. 旧版的HLog是实 ...
随机推荐
- Docker容器进入的4种方式(转)
在使用Docker创建了容器之后,大家比较关心的就是如何进入该容器了,其实进入Docker容器有好几多种方式,这里我们就讲一下常用的几种进入Docker容器的方法. 进入Docker容器比较常见的几种 ...
- Android应用的基本原理
原文:http://android.eoe.cn/topic/android_sdk 应用基础-Application Fundamentals Android应用程序以java作为开发语言.用And ...
- 复习:JSP基本的语法(JSP凝视 + JSP指令 + JSP脚本元素 + JSP动作元素)
JSP原理: 1. 对于每个请求.jsp容器都会创建一个新的线程来处理它: 2. Servlet容器载入jsp后转换成的servlet(.class文件)是常驻内存的,所以对应速度一般比較 ...
- 使用 Apache Commons CLI 开发命令行工具示例
Apache Commons CLI 简介 Apache Commons CLI 是 Apache 下面的一个解析命令行输入的工具包,该工具包还提供了自动生成输出帮助文档的功能. Apache Com ...
- apache-tomcat-7.0.53-windows-x86或者x64:出现错误提示:(Unable to open the service 'tomcat7)或者(Failed installing 'Tomcat7' service) tomcat7 %1 不是有效的 Win32 应用程序。
具体 安装行动 :打开下令 行提醒 符窗口 => 进入Tomcat安装目次 ==> 进入bin目次 下==> 输入:service.bat install 即可而且tomcat_ho ...
- js获取IP地址方法总结
js代码获取IP地址的方法,如何在js中取得客户端的IP地址.原文地址:js获取IP地址的三种方法 http://www.jbxue.com/article/11338.html 1,js取得IP地址 ...
- 期盼已久的spring-net居然有新版本2.0.1-GA
https://github.com/spring-projects/spring-net/tree/spring-net-2.0.1-GA https://www.nuget.org/package ...
- 【iOS XMPP】使用XMPPFramewok(四):收发消息
转自:http://www.cnblogs.com/dyingbleed/archive/2013/05/16/3075105.html 收发消息 接收消息 通过实现 - (void)xmppStre ...
- 深入理解Linux内核-内核同步
内核基本的同步机制: 抢占内核的主要特点:一个在内核态运行的进程,可能在执行内核函数期间被另外一个进程取代. 内核抢占:Linux 2.6允许用户在编译内核的时候配置十分启用 进程临界区:每个进程中访 ...
- angular学习笔记(三十)-指令(1)-概述
之前在 angular学习笔记(十九)-指令修改dom 里面已经简单的提到了angular中的指令,现在来详细的介绍 '指令' 一.指令的创建: dirAppModule.directive('dir ...