客户端在进行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方法获取到位置信息。

  

hbase源码系列(三)Client如何找到正确的Region Server的更多相关文章

  1. 三 Client 如何找到正确的 Region Server

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

  2. hbase源码系列(二)HTable 探秘

    hbase的源码终于搞一个段落了,在接下来的一个月,着重于把看过的源码提炼一下,对一些有意思的主题进行分享一下.继上一篇讲了负载均衡之后,这一篇我们从client开始讲吧,从client到master ...

  3. hbase源码系列(十二)Get、Scan在服务端是如何处理

    hbase源码系列(十二)Get.Scan在服务端是如何处理?   继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...

  4. 9 hbase源码系列(九)StoreFile存储格式

    hbase源码系列(九)StoreFile存储格式    从这一章开始要讲Region Server这块的了,但是在讲Region Server这块之前得讲一下StoreFile,否则后面的不好讲下去 ...

  5. 11 hbase源码系列(十一)Put、Delete在服务端是如何处理

    hbase源码系列(十一)Put.Delete在服务端是如何处理?    在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTab ...

  6. 10 hbase源码系列(十)HLog与日志恢复

    hbase源码系列(十)HLog与日志恢复   HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢 ...

  7. HBase源码系列之HFile

    本文讨论0.98版本的hbase里v2版本.其实对于HFile能有一个大体的较深入理解是在我去查看"到底是不是一条记录不能垮block"的时候突然意识到的. 首先说一个对HFile ...

  8. hbase源码系列(十二)Get、Scan在服务端是如何处理?

    继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...

  9. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

随机推荐

  1. uiautomatorviewer 查看元素新思路

    用adb 命令把图片和uix获取出来,再导入uiautomatorviewer adb shell uiautomator dump /data/local/tmp/uidump.uixadb pul ...

  2. js关闭当前页面和给子页面的对象赋值

    代码如下: function saveData(){ //给父页面的对象赋值 frameElement.api.opener.document.getElementById("userNam ...

  3. AndroidStudio创建项目时一直处于building“project name”gradle project info的解决办法

    AndroidStudio创建项目,最后一步finish后,一直长时间处于building“project name”gradle project info,界面就一直停留在如图所示: 谷歌自家的产品 ...

  4. kibana 的search 的的搜索提示挡住输入框

    解决方案就是 [root@10-19-148-59 styles]# pwd /data/package/kibana/src/public/styles [root@10-19-148-59 sty ...

  5. Android:percent 布局

    Android 新引入的布局,百分比布局,Percent 布局 主要分为两种:PercentFrameLayout he PercentRelativeLayout 布局 通过 support 库引入 ...

  6. apache-tomcat-7.0.53-windows-x86或者x64:出现错误提示:(Unable to open the service 'tomcat7)或者(Failed installing 'Tomcat7' service) tomcat7 %1 不是有效的 Win32 应用程序。

    具体 安装行动 :打开下令 行提醒 符窗口 => 进入Tomcat安装目次 ==> 进入bin目次 下==> 输入:service.bat install 即可而且tomcat_ho ...

  7. [fixed] 解决 slf4j + log4j eclipse 可以打印日志,而在云服务器上不能打印

    今天发现服务上没有打印任何日志,而log4j已经设置为了INFO 很奇怪,在eclipse中是可以打印的,也能输出到单独的日志中 后来发现原来是冲突了 把log4j注释掉即可 保留slf4j即可

  8. nginx 并发数问题思考:worker_connections,worker_processes与 max clients

    我相信,很多人都跟我一样,看书都不会太细致也不太认真思考,感觉书中讲的东西都应该是对的,最近读书时我发现以前认为理所当然的东西事实上压根都没有弄明白,最终的结果是,书是别人的,书中的知识也是别人的. ...

  9. 【驱动】input子系统全面分析

    初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputC ...

  10. Vue项目页面跳转时候的,浏览器窗口上方的进度条显示

    1.安装: cnpm install --save nprogress 2.在main.js中引入: import NProgress from 'nprogress' import 'nprogre ...