1. 程式人生 > 其它 >hbase原始碼系列(三)Client如何找到正確的Region Server

hbase原始碼系列(三)Client如何找到正確的Region Server

  客戶端在進行put、delete、get等操作的時候,它都需要資料到底存在哪個Region Server上面,這個定位的操作是通過HConnection.locateRegion方法來完成的。

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這個類。

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
            }

  看一下MetaScanner.metaScan吧,它也是用了new了一個HTable

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

  然後根據有三種情況,根據情況來構建Scan的StartKey

  1.根據rowkey來掃描

  2.全表掃

  3.根據表的名來

  這裡講一下根據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());

   好,現在最後總結一下吧:

  (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方法獲取到位置資訊。