zorrock +

hbase源码分析(转)

客户端在进行put、delete、get等操作的时候,它都需要数据到底存在哪个Region Server上面,这个定位的操作是通过HConnection.locateRegion方法来完成的。

loc = hConnection.locateRegion(this.tableName, row.getRow());

这里我们首先要讲hbase的两张元数据表-ROOT-和.META.表,它们一个保存着region的分部信息,一个保存着region的详细信息。在《hbase实战》这本书里面详细写了查找过程总共有8步:

那在代码里面是怎么体验上述过程的呢?好,我们开始查看locateRegion这个方法,打开HConnectionManager这个类。

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);
      }
}

TableName.META_TABLE_NAME,这个就是我们要找的-ROOT-,在0.96里面它已经被取消了,取而代之的是META表中的第一个regionHRegionInfo.FIRST_META_REGIONINFO,它位置在zk的meta-region-server节点当中的。 好吧,再回到代码里面,我们这里肯定是走else这个路径,我们进入locateRegionInMeta看看。 代码好长啊,我们一点一点看吧,先从缓存里面找,把tableName和rowkey传进去。

if (useCache) {
    location = getCachedLocation(tableName, row);
    if (location != null) {
       return location;
    }
}

这里的cache是这样组织的Map<tableName, SoftValueSortedMap<rowkey, HRegionLocation»,通过tableName获得它的基于rowkey的子map,这个map是按照key排好序的,如果找不到合适的key,就找比它稍微小一点的key。 接下来就是一个for循环了,默认是尝试31次.

HRegionLocation metaLocation = null;
      try {
        // locate the meta region 还好这个不是玩递归,直接获取meta表所在的位置
        metaLocation = locateRegion(parentTable, metaKey, true, false);
        if (metaLocation == null) continue;
     // 通过这方法可以获得Region Server,超值啊
        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);
          
     }

从上面的代码分析,它在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方法把满足条件的全都缓存起来。下面是条件,有兴趣的人可以看一下,我折叠起来。

HRegionInfo regionInfo = MetaScanner.getHRegionInfo(result);
  if (regionInfo == null) {
    return true;
  }

  // possible we got a region of a different table...
  if (!regionInfo.getTable().equals(tableName)) {
    return false; // stop scanning
  }
  if (regionInfo.isOffline()) {
    // don't cache offline regions
    return true;
  }

  ServerName serverName = HRegionInfo.getServerName(result);
  if (serverName == null) {
    return true; // don't cache it
  }

##View Code 看一下MetaScanner.metaScan吧,它也是用了new了一个HTable

HTable metaTable = new HTable(TableName.META_TABLE_NAME, connection, null);

然后根据有三种情况,根据情况来构建Scan的StartKey

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

byte[] searchRow = HRegionInfo.createRegionName(tableName, row, HConstants.NINES, false);
        Result startRowResult = metaTable.getRowOrBefore(searchRow, HConstants.CATALOG_FAMILY);
        HRegionInfo regionInfo = getHRegionInfo(startRowResult);
        byte[] rowBefore = regionInfo.getStartKey();
        startRow = HRegionInfo.createRegionName(tableName, rowBefore, HConstants.ZEROES, false);
下面就开始Scan了,这个Scan的代码,和我们平常用HTable来扫描表是一样的。

final Scan scan = new Scan(startRow).addFamily(HConstants.CATALOG_FAMILY);
  int rows = Math.min(rowLimit, configuration.getInt(HConstants.HBASE_META_SCANNER_CACHING,
    HConstants.DEFAULT_HBASE_META_SCANNER_CACHING));
  scan.setCaching(rows);
// Run the scan
  scanner = metaTable.getScanner(scan);
  Result result = null;
  int processedRows = 0;
  while ((result = scanner.next()) != null) {
     // 用visitor.processRow来过滤不符合的result
    if (visitor != null) {
      if (!visitor.processRow(result)) break;
    }
    processedRows++;
    if (processedRows >= rowUpperLimit) break;
  }
  如果没用缓存的情况,就只能走接口的方式了,直接从服务器去了,如果这都找不着,这一次就算结束了。

regionInfoRow = ProtobufUtil.getRowOrBefore(service,metaLocation.getRegionInfo().getRegionName(), metaKey, HConstants.CATALOG_FAMILY);

// 下面是具体的实现
GetRequest request = RequestConverter.buildGetRowOrBeforeRequest(regionName, row, family);
GetResponse response = client.get(null, request);
if (!response.hasResult()) return null;
return toResult(response.getResult());

好,现在最后总结一下吧:

点击查看评论

Blog

Opinion

Project