分享

hbase源码系列(三)Client如何找到正确的Region Server

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

问题导读
1.hbase的两张元数据表查找过程是怎样的?
2.如何根据情况来构建Scan的StartKey?









客户端在进行put、delete、get等操作的时候,它都需要数据到底存在哪个Region Server上面,这个定位的操作是通过HConnection.locateRegion方法来完成的。
  1. loc = hConnection.locateRegion(this.tableName, row.getRow());
复制代码

这里我们首先要讲hbase的两张元数据表-ROOT-和.META.表,它们一个保存着region的分部信息,一个保存着region的详细信息。在《hbase实战》这本书里面详细写了查找过程总共有8步:
  (1)客户端首先查询zookeeper -ROOT-表在哪里
  (2)zookeeper告诉客户端,-ROOT-在RegionServer RS1上面
  (3)客户端向RS1发起查询,哪一个.META表可以查到T1表里面的00009
  (4)RS1上的-ROOT-告诉客户端在RS3上面的.META. region M2可以找到
  (5)客户端向RS3上的.META. region M2查询T1表的00009行数据在哪个region上,哪一个Region Server可以提供服务
  (6)RS3告诉客户端,在RS3上面的region T1R3
  (7)客户端向RS3上面的region T1R3发起请求,我要读取00009行
  (8)RS3上的region T1R3把数据发给客户端,行,拿去吧

那在代码里面是怎么体验上述过程的呢?好,我们开始查看locateRegion这个方法,打开HConnectionManager这个类。
  1. private HRegionLocation locateRegion(final TableName tableName,
  2.       final byte [] row, boolean useCache, boolean retry) {
  3.       if (tableName.equals(TableName.META_TABLE_NAME)) {
  4.         return this.registry.getMetaRegionLocation();
  5.       } else {
  6.         // Region not in the cache - have to go to the meta RS
  7.         return locateRegionInMeta(TableName.META_TABLE_NAME, tableName, row,
  8.           useCache, userRegionLock, retry);
  9.       }
  10. }
复制代码
TableName.META_TABLE_NAME,这个就是我们要找的-ROOT-,在0.96里面它已经被取消了,取而代之的是META表中的第一个regionHRegionInfo.FIRST_META_REGIONINFO,它位置在zk的meta-region-server节点当中的。

  好吧,再回到代码里面,我们这里肯定是走else这个路径,我们进入locateRegionInMeta看看。
  代码好长啊,我们一点一点看吧,先从缓存里面找,把tableName和rowkey传进去。
  1. if (useCache) {
  2.     location = getCachedLocation(tableName, row);
  3.     if (location != null) {
  4.        return location;
  5.     }
  6. }
复制代码
这里的cache是这样组织的Map<tableName, SoftValueSortedMap<rowkey, HRegionLocation>>, 通过tableName获得它的基于rowkey的子map,这个map是按照key排好序的,如果找不到合适的key,就找比它稍微小一点的key。

接下来就是一个for循环了,默认是尝试31次
  1. HRegionLocation metaLocation = null;
  2.         try {
  3.           // locate the meta region 还好这个不是玩递归,直接获取meta表所在的位置
  4.           metaLocation = locateRegion(parentTable, metaKey, true, false);
  5.           if (metaLocation == null) continue;
  6.       // 通过这方法可以获得Region Server,超值啊
  7.           ClientService.BlockingInterface service = getClient(metaLocation.getServerName());
  8.       synchronized (regionLockObject)
  9.           if (useCache) {
  10.               location = getCachedLocation(tableName, row);
  11.               if (location != null) {
  12.                 return location;
  13.               }
  14.         // 如果表没有被禁用,就预加载缓存
  15.         if (parentTable.equals(TableName.META_TABLE_NAME)
  16.                   && (getRegionCachePrefetch(tableName))) {
  17.                 prefetchRegionCache(tableName, row);
  18.               }
  19.         // 如果缓存中有,就从缓存中取
  20.               location = getCachedLocation(tableName, row);
  21.               if (location != null) {
  22.                 return location;
  23.               }
  24.           }else {
  25.         // 不需要缓存就在缓存中删掉
  26.               forceDeleteCachedLocation(tableName, row);
  27.             
  28.       }
复制代码

从上面的代码分析,它在prefetchRegionCache方法预先缓存了和表和row相关的位置信息,核心的代码如下:
  1. MetaScannerVisitor visitor = new MetaScannerVisitorBase() {
  2.         public boolean processRow(Result result) throws IOException {
  3.         // 把result转换为regionInfo
  4.        HRegionInfo regionInfo = MetaScanner.getHRegionInfo(result);
  5.             long seqNum = HRegionInfo.getSeqNumDuringOpen(result);
  6.             HRegionLocation loc = new HRegionLocation(regionInfo, serverName, seqNum);
  7.             // cache this meta entry
  8.             cacheLocation(tableName, null, loc);
  9.             return true;
  10.         }
  11.       };  MetaScanner.metaScan(conf, this, visitor, tableName, row, this.prefetchRegionLimit, TableName.META_TABLE_NAME);
复制代码

这里面的核心代码只有两行,实现一个MetaScannerVisitor,然后传入到MetaScanner.metaScan扫描一下,metaScan会调用visiter的processRow方法,processRow方法把满足条件的全都缓存起来。下面是条件,有兴趣的人可以看一下。
  1. HRegionInfo regionInfo = MetaScanner.getHRegionInfo(result);
  2.             if (regionInfo == null) {
  3.               return true;
  4.             }
  5.             // possible we got a region of a different table...
  6.             if (!regionInfo.getTable().equals(tableName)) {
  7.               return false; // stop scanning
  8.             }
  9.             if (regionInfo.isOffline()) {
  10.               // don't cache offline regions
  11.               return true;
  12.             }
  13.             ServerName serverName = HRegionInfo.getServerName(result);
  14.             if (serverName == null) {
  15.               return true; // don't cache it
  16.             }
复制代码

看一下MetaScanner.metaScan吧,它也是用了new了一个HTable
  1. HTable metaTable = new HTable(TableName.META_TABLE_NAME, connection, null);
复制代码

然后根据有三种情况,根据情况来构建Scan的StartKey
  1.根据rowkey来扫描
  2.全表扫
  3.根据表的名来

  这里讲一下根据rowkey来扫描吧,别的都很简单,它用的是HTable的getRowOrBefore来找到这个Row,只不过因为它是meta表,可以从zk上直接找到位置。

  1. byte[] searchRow = HRegionInfo.createRegionName(tableName, row, HConstants.NINES, false);
  2.         Result startRowResult = metaTable.getRowOrBefore(searchRow, HConstants.CATALOG_FAMILY);
  3.         HRegionInfo regionInfo = getHRegionInfo(startRowResult);
  4.         byte[] rowBefore = regionInfo.getStartKey();
  5.         startRow = HRegionInfo.createRegionName(tableName, rowBefore, HConstants.ZEROES, false);
复制代码

下面就开始Scan了,这个Scan的代码,和我们平常用HTable来扫描表是一样的。
  1. final Scan scan = new Scan(startRow).addFamily(HConstants.CATALOG_FAMILY);
  2.       int rows = Math.min(rowLimit, configuration.getInt(HConstants.HBASE_META_SCANNER_CACHING,
  3.         HConstants.DEFAULT_HBASE_META_SCANNER_CACHING));
  4.       scan.setCaching(rows);
  5.       // Run the scan
  6.       scanner = metaTable.getScanner(scan);
  7.       Result result = null;
  8.       int processedRows = 0;
  9.       while ((result = scanner.next()) != null) {
  10.      // 用visitor.processRow来过滤不符合的result
  11.         if (visitor != null) {
  12.           if (!visitor.processRow(result)) break;
  13.         }
  14.         processedRows++;
  15.         if (processedRows >= rowUpperLimit) break;
  16.       }
复制代码

如果没用缓存的情况,就只能走接口的方式了,直接从服务器去了,如果这都找不着,这一次就算结束了。
  1. regionInfoRow = ProtobufUtil.getRowOrBefore(service,metaLocation.getRegionInfo().getRegionName(), metaKey, HConstants.CATALOG_FAMILY);
  2. // 下面是具体的实现
  3. GetRequest request = RequestConverter.buildGetRowOrBeforeRequest(regionName, row, family);
  4. GetResponse response = client.get(null, request);
  5. if (!response.hasResult()) return null;
  6. return toResult(response.getResult());
复制代码

好,现在最后总结一下吧:
  (1)要查询数据时候,在locateRegion方法要先走locateRegionInMeta这段
  (2)从zk当中获取meta表的位置,通过这个位置信息ServerName,获得Region Server的接口,但是这里先不用,留给不用缓存的情况用的
  (3)使用缓存的话,如果这个表没被禁用,就先把要定位的整个表的region的位置信息,全部缓存起来
  (4)在缓存表的过程当中,我们要借助new HTable(TableName.META_TABLE_NAME, connection, null)来计算startKey和扫描。
  (5)把扫描到的表相关的位置信息缓存起来,缓存之后取的过程这里忘了交代了,通过表名找到表对应的一个HRegionInfo,HRegionInfo里面包括startKey和stopKey,用rowkey一比对就知道是哪个region了。
  (6)不用缓存的情况,就走接口的方式,构造一个GetRequest,调用Region Server里面的get方法获取到位置信息。





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 下一条