• 三 Client 如何找到正确的 Region Server


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

    Connection.locateRegion方法来完成的。

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

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

      (1)客户端-》zk -(ROOT-表在哪)

      (2)zk->客户端(-ROOT-RegionServer X node中)

      (3)客户端->x RegionServer 发起查询,-> 去那个META表可以查到T1表里的  YYYY rowkey

      (4)x RegionServer -ROOT-> 客户端 (RegionServer在 X1 上的.META. region 的 M2可以找到

      (5)客户端-》RegionServer  X1 上的.META. region M2查询T1表的YYYY key行数据在哪个region上,哪一个Region Server可以提供服务

      (6)RegionServer  X1 告诉客户端,在RegionServer  X1 上面的region RG33

      (7)客户端向RegionServer  X1上面的region RG33 发起请求,我要读取00009行

      (8)RegionServer  X1 的 region RG33 把数据发给客户端;

      代码里面查看 HConnectionManager.locateRegion() 的实现过程

    复制代码
    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 regionsreturn 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当中获取 root - 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 客户端设置缓存优化查询。

    其实查询无非是一个 HBase 的 RPC 计算公式 、然后给API 提供值。
    RPCs = (Rows * Cols per Row) / Min(Cols per Row, Batch Size)/ Scanner Caching 。

    Hadoop的MR运算中,Hbase可以作为输入数据源参与运算,其中作为HTable的迭代器Scan有几个使用技巧

    涉及的方法如下:

    public void setBatch(int batch)public void setCaching(int caching)public void setCacheBlocks(boolean cacheBlocks)

    public void setBatch(int batch) :

    为设置获取记录的列个数,默认无限制,也就是返回所有的列

    public void setCaching(int caching):

    每次从服务器端读取的行数,默认为配置文件中设置的值

    public void setCacheBlocks(boolean cacheBlocks):

    为是否缓存块,默认缓存,我们分内存,缓存和磁盘,三种方式,一般数据的读取为内存->缓存->磁盘,当MR的时候为非热点数据,因此不需要缓存

    因此在MR的时候最好设置如下:

    scan.setCacheBlocks(false);
    scan.setCaching(200);//大了占内存,但是rpc少
    scan.setBatch(6);//你需要的列
    在查询的时候,按照查询的列数动态设置batch,如果全查,则根据自己所有的表的大小设置一个折中的数值,caching就和分页的值一样就行。

    当caching和batch都为1的时候,我们要返回10行具有20列的记录,就要进行201次RPC,因为每一列都作为一个单独的Result来返回,这样是我们不可以接受的。


     




    God has given me a gift. Only one. I am the most complete fighter in the world. My whole life, I have trained. I must prove I am worthy of someting. rocky_24
  • 相关阅读:
    hdu1506(dp)
    windows下安装JMeter
    phpstudy 80端口被占用,修改端口
    久违的phpstorm
    软件项目版本号的命名规则及格式
    phpstudy 局域网访问
    java+eclipse+selenium环境搭建
    软件测试方法汇总
    功能测试大全
    如何有效地描述软件缺陷(Defect)?
  • 原文地址:https://www.cnblogs.com/rocky24/p/4bb3d0ee869149adc11787dd1188b4fe.html
Copyright © 2020-2023  润新知