转载自:http://ronxin999.blog.163.com/blog/static/42217920201311363453387/
参数属性
hbase.master.startup.retainassign master :
启动时的region分配算法选择,默认是即根据region在meta表中是归属那个RS,还是分配到
原来的RS,不存在的,则随机选择,否则随机生成
hbase.assignment.maximum.attempts master分配region尝试的次数,默认为10次
hbase.master.assignment.timeoutmonitor.period 10000,即10s
两个状态:
zkNode
zookeeper 上的path: hbase/unassigned/regionName,
zkNode是由master创建(开始分配region的时候)。和删除(region在RS成功上线)。
RegionState
Master的内存状态
MASTER
1 Assignment在分配region时,首先设置该region在zk上的状态为M_ZK_REGION_OFFLINE,
RegionState 对应的状态为OFFLINE,有一个需要注意的是 因为master发现region在PENDING_OPEN/OPENING很长时间时,会强制设置zknode为 M_ZK_REGION_OFFLINE ,
但同时region server可能已经打开了相关的region,并设置zknode为RS_ZK_REGION_OPENED,所以如果因为 opengregion的时间过长,而导致reassign region时,而且region state为PENDING_OPEN/OPENING时,只需要直接更新region state为PENDING_OPEN即可。不需要设置zknode为M_ZK_REGION_OFFLINE
2 创建分配region的regionPlan,创建前需要先检查是否有Draining Server和Dead Server,如果有,需要从server list中remove掉。如果是force assign/老的regionPlan不存在/老的regionPlan的目标RS是Draining的 ,则需要重新创建一个regionPlan,目标rs则随机选择一个。
3 得到regionPlan后,master更新regionState状态为PENDING_OPEN。
4 发送RPC请求RS 开始做openRegion的工作。
注意:master是通过StartupBulkAssigner来并发分配多个RS上的regions的。为每个RS创建一个
SingleServerBulkAssigner runnable交给线程池来执行。同样每个RS对多个regions也是并行加载的
是通过ExecutorService 线程池来执行每个OpenRegionHandler的。
TimeoutMonitor
主要是监控regionsInTransition中的RegionState是否超时,如果超时,则重新分配region的上线工作。
TimerUpdater
timeoutMonitor负责监听在rit状态中的region 分配是否超时,如果有一个region分配到多个region,但是每个RS加载region是同步的,这样可能一个region在加载的过程中,其他的region有可能被timeoutMonitor发现超时,所以在master在发现RS成功上线一个region后,会通过TimeUpdater来更新该RS上其它待加载的region的
时间戳。
RegionServer
1 先检查该region在RS是否正在事物中。
2 检查该region是否在RS的上线region列表,如果存在,还需要检查该region在meta表中的标记的是哪个RS,如果meta表中标记的RS是当前要打开该region的RS,则直接返回region已经在线的状态ALREADY_OPENED给Master。否则需要把该region从online region列表中remove掉。
3 添加region的name到regionsInTransitionInRS中
4 根据region的类型创建不同的runnable交给线程池执行,根据region的类型,有如下三种线程:
Root Region :OpenRootHandler
Meta Region :OpenMetaHandler
用户Region :OpenRegionHandler
5 RS返回OPENED状态给master,返回OPENED只是告诉Master,RS已经接受了该region,并正在打开。
但并不说明已经上线了给region。如果返回ALREADY_OPENED,则说明已经上线,
OpenRegionHandler
1 handler 首先修改zknode的状态从M_ZK_REGION_OFFLINE到RS_ZK_REGION_OPENING状态,这个需要
校验版本和原始状态。
2 开始打开region。打开region的操作比较耗时,详细过程为
2.1 持久化region信息到disk,目录为region path下的.regioninfo文件,方便用来recover 系统META表。
如果已经存在,则不需要写。写的时候也是先写到region的.tmp/.regioninfo目录下,写成功后,
在重命名 回来到region/.regioninfo。这样写是因为怕在写的时候关闭前,datanode当机,导致后面
的region恢复会失败。因为写文件需要在namenode上注册获得文件的租约。
2.2 清理该region目录下的.tmp文件。
2.3 初始化所有的store。
2.4 store初始化成功后,开始恢复HLog的增量数据。hlog修改日志数据保存在
region-path/recovered.edits目录下以.temp结尾的文件。在读这些文件的时候,会过滤掉以数字
开头但不 是以.temp结尾的文件,这些文件时错误或者无效的文件。
2.5 读日志文件内容到memstore,因为每个store都对应一个LogSeqNum,所以恢复时以最小的为准,
每个hlog文件保护多个HLog.Entry(HLogKey,WALEdit的键值对)。每个HLogKey都记录
一个 LogSeqNum,在恢复时需要跳过比store最小的LogSeqNum。因为这些已经flush到HFile了。
具体恢复到哪个store,是根据WALEdit中的KeyValue的family来决定的。在不断恢复WALEdit的
过程中,也会检查memstore的大小,如果超过了flush的临界值,则flush。
2.6 清理split目录region/.splits目录
2.7 删除.merges目录
2.8 创建split策略。
3 打开region完成后,需要判断是否成功,如果失败,则更新zknode的状态
为 RS_ZK_REGION_FAILED_OPEN后,返回,RS打开region这个任务也就结束了。
4 如果打开成功,则开始上线region,主要涉及到更新zknode的状态,把region的位置添加到META表。
详细过程如下:
4.1 刷新zknode的状态,即重新更新为RS_ZK_REGION_OPENING,为什么说重新更新,是因为到这一步,
zknode的状态同样是RS_ZK_REGION_OPENING。好多人可能会有疑问,为什么要做这一步呢。原因是
防止master那边认为该region的分配认为超时。注意每更新zknode的状态,都会重新时间戳,
这样Master端的zookeeper watch收到更新事件时,也会更新对应的region state的时间戳。
这样就不会被TimeoutMonitor认为超时了。
4.2 在上面flush成功后,进入到更新META表,这里可能要多花点时间,因为META表可能还没有上线,
或者 META表的region所在的region刚好crashed了,这都需要等待META表恢复。所有这里会取用一个
单独的线程:PostOpenDeployTasksThread来更新META表,而主线程定期更新zknode的状态
为RS_ZK_REGION_OPENING,同样是为了防止不让master认为该region在分配过程中超时。
PostOpenDeployTasksThread的任务从名字就可以看出,它是来负责往META表添加一条region的
位置记录。核心的代码如下:
final boolean daughter)
throws KeeperException, IOException {
checkOpen();
LOG.info("Post open deploy tasks for region=" + r.getRegionNameAsString() +
", daughter=" + daughter);
// Do checks to see if we need to compact (references or too many files)
for (Store s : r.getStores().values()) {
//如果是分裂的,则会进行compact动作。
if (s.hasReferences() || s.needsCompaction()) {
getCompactionRequester().requestCompaction(r, s, "Opening Region");
}
}
// Update ZK, ROOT or META
if (r.getRegionInfo().isRootRegion()) {
RootLocationEditor.setRootLocation(getZooKeeper(),
this.serverNameFromMasterPOV);
} else if (r.getRegionInfo().isMetaRegion()) {
MetaEditor.updateMetaLocation(ct, r.getRegionInfo(),
this.serverNameFromMasterPOV);
} else {
if (daughter) {
// If daughter of a split, update whole row, not just location.
MetaEditor.addDaughter(ct, r.getRegionInfo(),
this.serverNameFromMasterPOV);
} else {
MetaEditor.updateRegionLocation(ct, r.getRegionInfo(),
this.serverNameFromMasterPOV);
}
}
LOG.info("Done with post open deploy task for region=" +
r.getRegionNameAsString() + ", daughter=" + daughter);
}
如果你看过region分裂的代码,应该有印象,因为在region分裂完后,也会执行这个方法来compact store
主线程给PostOpenDeployTasksThread线程的时间为timeout = assignmentTimeout的10倍,assignmentTimeout
可以通过参数hbase.master.assignment.timeoutmonitor.period来设置,默认为10s,主线程每assignmentTimeout/3的时间会更新zknode的状态,如果在timeout 的时间内PostOpenDeployTasksThread
线程还没有完成,则中断该线程,然后join,等待线程结束。代码入下:
boolean updateMeta(final HRegion r) {
if (this.server.isStopped() || this.rsServices.isStopping()) {
return false;
}
// Object we do wait/notify on. Make it boolean. If set, we're done.
// Else, wait.
final AtomicBoolean signaller = new AtomicBoolean(false);
PostOpenDeployTasksThread t = new PostOpenDeployTasksThread(r,
this.server, this.rsServices, signaller);
t.start();
int assignmentTimeout = this.server.getConfiguration().
getInt("hbase.master.assignment.timeoutmonitor.period", 10000);
// Total timeout for meta edit. If we fail adding the edit then close out
// the region and let it be assigned elsewhere.
long timeout = assignmentTimeout * 10;
long now = System.currentTimeMillis();
long endTime = now + timeout;
// Let our period at which we update OPENING state to be be 1/3rd of the
// regions-in-transition timeout period.
long period = Math.max(1, assignmentTimeout/ 3);
long lastUpdate = now;
boolean tickleOpening = true;
while (!signaller.get() && t.isAlive() && !this.server.isStopped() &&
!this.rsServices.isStopping() && (endTime > now)) {
long elapsed = now - lastUpdate;
if (elapsed > period) {
// Only tickle OPENING if postOpenDeployTasks is taking some time.
lastUpdate = now;
tickleOpening = tickleOpening("post_open_deploy");
}
synchronized (signaller) {
try {
signaller.wait(period);
} catch (InterruptedException e) {
// Go to the loop check.
}
}
now = System.currentTimeMillis();
}
// Is thread still alive? We may have left above loop because server is
// stopping or we timed out the edit. Is so, interrupt it.
if (t.isAlive()) {
if (!signaller.get()) {
// Thread still running; interrupt
LOG.debug("Interrupting thread " + t);
t.interrupt();
}
try {
t.join();
} catch (InterruptedException ie) {
LOG.warn("Interrupted joining " +
r.getRegionInfo().getRegionNameAsString(), ie);
Thread.currentThread().interrupt();
}
}
// Was there an exception opening the region? This should trigger on
// InterruptedException too. If so, we failed. Even if tickle opening fails
// then it is a failure.
return ((!Thread.interrupted() && t.getException() == null) && tickleOpening);
}
5 如果更新meta表成功,则更新zknode的状态为RS_ZK_REGION_OPENED。把该region添加到在线列表 online,并从regionsInTransitionInRS中remove掉该region的记录。如果更新meta失败,则需要把打开的region close掉。同时zknode的状态更新为RS_ZK_REGION_FAILED_OPEN。到此,RS上线region的过程就算结束了。