。@TOC
一、初步认识region定位在 Hbase 中,表的所有行都是按照 RowKey 的字典序排列的,表在行的方向上分割为多个分区(Region),如下图所示。
每张表一开始只有一个 Region,但是随着数据的插入,Hbase 会根据一定的规则将表进行水平拆分,形成两个 Region。当表中的行越来越多时,就会产生越来越多的 Region,而这些 Region 无法存储到一台机器上时,则可将其分布存储到多台机器上。
有了 Region 标识符,就可以唯一标识每个 Region。为了定位每个 Region 所在的位置,可以构建一张映射表。
映射表的每个条目包含两项内容,一项是 Region 标识符,另一项是 Region 服务器标识。这个条目就表示 Region 和 Region 服务器之间的对应关系,从而就可以使用户知道某个 Region 存储在哪个 Region 服务器中。这个映射表包含了关于 Region 的元数据,因此也被称为“元数据表”,又名“meta表”。
meta 表中的每一行记录了一个 Region 的信息。RowKey 包含表名、起始行键和时间戳信息,中间用逗号隔开,第一个 Region 的起始行键为空。时间戳之后用.隔开的为分区名称的编码字符串,该信息是由前面的表名、起始行键和时间戳进行字符串编码后形成的。
meta 表里有一个列族 info。info 包含了三个列,分别为 RegioninfoServer 和 Serverstartcode。Regionlnfo中记录了 Region 的详细信息,包括行键范围 StartKey 和 EndKey、列族列表和属性。
Server 记录了管理该 Region 的 Region 服务器的地址,如 localhost:16201。Serverstartcode 记录了 Region 服务器开始托管该 Region 的时间。
当用户表特别大时,用户表的 Region 也会非常多。meta 表存储了这些 Region 信息,也变得非常大。meta 表也需要划分成多个 Region,每个 meta 分区记录一部分用户表和分区管理的情况。
在 Hbase 的早期设计中,Region 的查找是通过三层架构来进行查询的,即在集群中有一个总入口 ROOT 表,记录了 meta 表分区信息及各个入口的地址。这个 ROOT 表存储在某个 Region 服务器上,但是它的地址保存在 ZooKeeper 中。
这种早期的三层架构通过先找到 ROOT 表,从中获取分区 meta 表位置;然后再获取分区 meta 表信息,找出 Region 所在的 Region 服务器。
从 0.96 版本以后,三层架构被改为二层架构,去掉了 ROOT 表,同时 ZooKeeper 中的 /hbase/root-region-server 也被去掉。meta 表所在的 Region 服务器信息直接存储在 ZooKeeper 中的 /hbase/meta-region-server 中。
客户端通过 ZooKeeper 获取 meta 表分区存储的地址,首先在对应的 Region 服务器上获取 meta 表的信息,得到所需的表和行键所在的 Region 信息,然后从 Region 服务器上找到所需的数据。
一般客户端获取 Region 信息后会进行缓存,用户下次再查询不必从 ZooKeeper 开始寻址。
loc = hConnection.locateRegion(this.tableName, row.getRow());
一、
private HRegionLocation locateRegion(final TableName tableName,
final byte [] row, boolean useCache, boolean retry) {
if (tableName.equals(TableName.meta_TABLE_NAME)) {
return this.registry.getmetaRegionLocation();
} else {
// Region not in the cache - have to go to the meta RS
return locateRegionInmeta(TableName.meta_TABLE_NAME, tableName, row,
useCache, userRegionLock, retry);
}
}
这是HConnectionManager类,TableName.meta_TABLE_NAME,这个就是我们要找的-ROOT-,在0.96里面它已经被取消了,取而代之的是meta表中的第一个regionHRegionInfo.FIRST_meta_REGIONINFO,它位置在zk的meta-region-server节点当中的。
private RegionLocations locateRegionInmeta(TableName tableName, byte[] row, boolean useCache,
boolean retry, int replicaId) throws IOException {
// If we are supposed to be using the cache, look in the cache to see if we already have the
// region.
if (useCache) {
RegionLocations locations = getCachedLocation(tableName, row);
if (locations != null && locations.getRegionLocation(replicaId) != null) {
return locations;
}
}
byte[] metaStartKey = RegionInfo.createRegionName(tableName, row, HConstants.NINES, false);
byte[] metaStopKey =
//...
这是locateRegionInmeta类,调用locateRegionInmeta函数,首先查找cache中是否有table对应的row,对应缓存中的Table对应的startkey与endkey,判断row是否在某个region中;若能找到,直接返回该regionlocation信息
这里的cache是Map
下面是for循环,默认尝试31次
HRegionLocation metaLocation = null;
try {
metaLocation = locateRegion(parentTable, metaKey, true, false);
if (metaLocation == null) continue;
ClientService.BlockingInterface service = getClient(metaLocation.getServerName());
synchronized (regionLockObject)
if (useCache) {
location = getCachedLocation(tableName, row);
if (location != null) {
return location;
}
if (parentTable.equals(TableName.meta_TABLE_NAME)
&& (getRegionCachePrefetch(tableName))) {
prefetchRegionCache(tableName, row);
}
location = getCachedLocation(tableName, row);
if (location != null) {
return location;
}
}else {
forceDeleteCachedLocation(tableName, row);
}
metaLocation = locateRegion(parentTable, metaKey, true, false); locate the meta region直接获取meta表所在的位置。通过 ClientService.BlockingInterface service = getClient(metaLocation.getServerName());方法可以获得。如果表没有被禁用,就预加载缓存; 如果缓存中有,就从缓存中取;不需要缓存就在缓存中删掉
在prefetchRegionCache方法预先缓存了和表和row相关的位置信息,核心的代码如下:
metaScannerVisitor visitor = new metaScannerVisitorbase() {
public boolean processRow(Result result) throws IOException {
// 把result转换为regionInfo
HRegionInfo regionInfo = metaScanner.getHRegionInfo(result);
long seqNum = HRegionInfo.getSeqNumDuringOpen(result);
HRegionLocation loc = new HRegionLocation(regionInfo, serverName, seqNum);
// cache this meta entry
cacheLocation(tableName, null, loc);
return true;
}
}; metaScanner.metaScan(conf, this, visitor, tableName, row, this.prefetchRegionLimit, TableName.meta_TABLE_NAME);
实现一个metaScannerVisitor,然后传入到metaScanner.metaScan扫描一下,metaScan会调用visiter的processRow方法,processRow方法把满足条件的全都缓存起来。
二、
if (relocatemeta) {
relocateRegion(TableName.meta_TABLE_NAME, HConstants.EMPTY_START_ROW,
RegionInfo.DEFAULT_REPLICA_ID);
}
s.resetMvccReadPoint();
try (ReversedClientScanner rcs =
new ReversedClientScanner(conf, s, TableName.meta_TABLE_NAME, this, rpcCallerFactory,
rpcControllerFactory, getmetaLookupPool(), metaReplicaCallTimeoutScanInMicroSecond)) {
boolean tableNotFound = true;
for (;;) {
Result regionInfoRow = rcs.next();
if (regionInfoRow == null) {
if (tableNotFound) {
throw new TableNotFoundException(tableName);
} else {
throw new IOException(
"Unable to find region for " + Bytes.toStringBinary(row) + " in " + tableName);
}
}
tableNotFound = false;
// convert the row result into the HRegionLocation we need!
locations = metaTableAccessor.getRegionLocations(regionInfoRow);
if (locations == null || locations.getRegionLocation(replicaId) == null) {
throw new IOException("RegionInfo null in " + tableName + ", row=" + regionInfoRow);
}
RegionInfo regionInfo = locations.getRegionLocation(replicaId).getRegion();
if (regionInfo == null) {
throw new IOException("RegionInfo null or empty in " + TableName.meta_TABLE_NAME +
", row=" + regionInfoRow);
}
若cache没有开启或cache为空或没有找到,则需要构造一个metakey,该metakey是用来在meta表中查找Regionlocation的rowkey,构造rowkey=tableName+startkey+9999999999999+encode
三、
if (useCache) {
RegionLocations locations = getCachedLocation(tableName, row);
if (locations != null && locations.getRegionLocation(replicaId) != null) {
return locations;
}
} else {
// If we are not supposed to be using the cache, delete any existing cached location
// so it won't interfere.
// We are only supposed to clean the cache for the specific replicaId
metaCache.clearCache(tableName, row, replicaId);
}
在尝试次数范围内,清空metaCache中的对应本次查询的tableName和row的信息;
metaCache.clearCache(tableName,row);
四、
rcs = new ClientSmallReversedScanner(conf, s, TableName.meta_TABLE_NAME, this,
rpcCallerFactory, rpcControllerFactory, getBatchPool(), 0);
// 通过scanner的next()方法,获取唯一的结果regionInfoRow
regionInfoRow = rcs.next();
} finally {
// 关闭ClientSmallReversedScanner
if (rcs != null) {
rcs.close();
}
}
if (regionInfoRow == null) {// 如果regionInfoRow为空,直接抛出TableNotFoundException异常
throw new TableNotFoundException(tableName);
}
// convert the row result into the HRegionLocation we need!
// 将Result转换为我们需要的RegionLocations,即regionInfoRow->locations
RegionLocations locations = metaTableAccessor.getRegionLocations(regionInfoRow);
if (locations == null || locations.getRegionLocation(replicaId) == null) {
throw new IOException("HRegionInfo was null in " +
tableName + ", row=" + regionInfoRow);
}
// 从locations中获取Region信息HRegionInfo
HRegionInfo regionInfo = locations.getRegionLocation(replicaId).getRegionInfo();
if (regionInfo == null) {
throw new IOException("HRegionInfo was null or empty in " +
TableName.meta_TABLE_NAME + ", row=" + regionInfoRow);
}
// possible we got a region of a different table...
// 验证表名是否一致
if (!regionInfo.getTable().equals(tableName)) {
throw new TableNotFoundException(
"Table '" + tableName + "' was not found, got: " +
regionInfo.getTable() + ".");
}
// 验证Rgion是否已分裂
if (regionInfo.isSplit()) {
throw new RegionOfflineException("the only available region for" +
" the required row is a split parent," +
" the daughters should be online soon: " +
regionInfo.getRegionNameAsString());
}
// 验证Rgion是否已下线
if (regionInfo.isOffline()) {
throw new RegionOfflineException("the region is offline, could" +
" be caused by a disable table call: " +
regionInfo.getRegionNameAsString());
}
// 从locations中根据replicaId获取ServerName
ServerName serverName = locations.getRegionLocation(replicaId).getServerName();
if (serverName == null) {
throw new NoServerForRegionException("No server address listed " +
"in " + TableName.meta_TABLE_NAME + " for region " +
regionInfo.getRegionNameAsString() + " containing row " +
Bytes.toStringBinary(row));
}
// 验证ServerName是否已死亡
if (isDeadServer(serverName)){
throw new RegionServerStoppedException("hbase:meta says the region "+
regionInfo.getRegionNameAsString()+" is managed by the server " + serverName +
", but it is dead.");
}
// Instantiate the location
// 缓存获得的位置信息
cacheLocation(tableName, locations);
return locations;
} catch (TableNotFoundException e) {
// if we got this error, probably means the table just plain doesn't
// exist. rethrow the error immediately. this should always be coming
// from the HTable constructor.
throw e;
} catch (IOException e) {
ExceptionUtil.rethrowIfInterrupt(e);
if (e instanceof RemoteException) {
e = ((RemoteException)e).unwrapRemoteException();
}
if (tries < localNumRetries - 1) {
if (LOG.isDebugEnabled()) {
LOG.debug("locateRegionInmeta parentTable=" +
TableName.meta_TABLE_NAME + ", metaLocation=" +
", attempt=" + tries + " of " +
localNumRetries + " failed; retrying after sleep of " +
ConnectionUtils.getPauseTime(this.pause, tries) + " because: " + e.getMessage());
}
} else {
throw e;
}
// only relocate the parent region if necessary
if(!(e instanceof RegionOfflineException ||
e instanceof NoServerForRegionException)) {
relocateRegion(TableName.meta_TABLE_NAME, metaKey, replicaId);
}
}
try{
// 当前线程休眠一段时间,再次重试,休眠的时间与pause和tries有关,越往后,停顿时间一般越长(波动时间除外)
Thread.sleep(ConnectionUtils.getPauseTime(this.pause, tries));
} catch (InterruptedException e) {
throw new InterruptedIOException("Giving up trying to location region in " +
"meta: thread is interrupted.");
}
}
构造一个Scan,scan的起始行为上述metaKey,并且是一个反向小scan,即reversed small Scan;确定重试上限次数localNumRetries:如果标志位retry为true的话,重试上限次数localNumRetries取numTries,即取参数hbase.client.retries.number,参数未配置的话默认为31;在一个循环内,当重试次数tries未达到上限localNumRetries且未定位到对应Region位置信息时:先判断重试次数tries是否达到上限localNumRetries,达到的话,直接抛出NoServerForRegionException异常;根据是否支持从缓存中取来判断:如果支持使用缓存的话,每次再从缓存中取一遍,存在即返回,否则继续;如果我们不支持使用缓存,删除任何存在的相关缓存,以确保它不会干扰我们的查询,调用metaCache的clearCache()方法,根据tableName和row来删除;
构造ClientSmallReversedScanner实例rcs,从meta表中查找,而meta表的表名固定为hbase:meta,它的namespace为"meta",qualifier为"meta",获取scanner,注意,这一步实际上是一个内嵌的scan,它也需要根据表和行进行Region的定位,而这个表就是Hbase中的meta表,既然从meta表中查找数据,那么就又折回到上面针对meta表和非meta标的的if…else…判断了,关于meta表的定位我们稍等再讲;
通过scanner的next()方法,获取唯一的结果regionInfoRow;
关闭ClientSmallReversedScanner;
如果regionInfoRow为空,直接抛出TableNotFoundException异常;
将Result转换为我们需要的RegionLocations,即regionInfoRow->locations;
从locations中获取Region信息HRegionInfo;
做一些必要的数据和状态校验,比如:验证表名是否一致;验证Rgion是否已分裂;验证Rgion是否已下线;从locations中根据replicaId获取ServerName,验证ServerName是否已死亡;
调用cacheLocation()方法缓存获得的位置信息locations,并返回;
如果中间出现异常,则当前线程休眠一段时间,再次重试,休眠的时间与pause和tries有关,越往后,停顿时间一般越长(波动时间除外)。
region定位的步骤:
1)用户通过查找zk(zookeeper)的/hbase/root-region-server节点来知道-ROOT-表在什么RegionServer上。
(2)访问-ROOT-表,查看需要的数据在哪个.meta.表上,这个.meta.表在什么RegionServer上。
(3)访问.meta.表查看查询的行健在什么Region范围里面。
(4)连接具体的数据所在的RegionServer,开始用Scan遍历row。



