栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

2021SC@SDUSC HBase(六)项目代码分析——Region机制(三)之Region定位

2021SC@SDUSC HBase(六)项目代码分析——Region机制(三)之Region定位

。@TOC

一、初步认识region定位

在 Hbase 中,表的所有行都是按照 RowKey 的字典序排列的,表在行的方向上分割为多个分区(Region),如下图所示。

每张表一开始只有一个 Region,但是随着数据的插入,Hbase 会根据一定的规则将表进行水平拆分,形成两个 Region。当表中的行越来越多时,就会产生越来越多的 Region,而这些 Region 无法存储到一台机器上时,则可将其分布存储到多台机器上。

meta表

有了 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 分区记录一部分用户表和分区管理的情况。

Region定位

在 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 开始寻址。

二、Region定位
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>, 通过tableName获得它的基于rowkey的子map,这个map是按照key排好序的,如果找不到合适的key,就找比它稍微小一点的key。
下面是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。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/423259.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号