HBase scan的客户端分析

HBase中rowkey是索引,任何对全表的扫描或是统计都需要用到scan接口。

本文主要探讨的是客户端是如何通过scan来扫描hbase的table的。

主要关注几个问题:

1.每一个Table可能不止一个region,分布在不同的regionserver上。客户端需要找到每个region的位置并与之通信;

2.Hbase是以append形式把数据写进去的,无论是写还是删除只是在keyvalue上打个标签,然后put进内存而已。等到region的内存满64m以后,会flush成一个新的文件写入磁盘。真正的数据删除操作会等到执行compact时候进行;那么在compact之前,在内存或者是storfile里面就很夹杂着很多delete或put的数据,甚至会有相同的数据。如何正确地识别它们,并且高效率的读取所想要的数据,这就是一个很大的问题;

3.客户端的scan可以配置几个参数maxversion,batch,caching。第一个是功能性需求,第二和第三个则对于client的性能有提高,这几个参数是如何作用的;

4.客户端起多个线程进行scan的时候如何做到每个线程不相互影响或者说数据不会紊乱

5.如何在保证正确性的前提下高效地scan是最重要的

首先来看看一个简单的scan的客户端程序

 

 public static void getAllData(HTable table) throws IOException {

    Scan s = new Scan();
    s.setMaxVersions();
    s.setBatch(1000);
    ResultScanner rs = table.getScanner(s);
    for (Result r : rs) {
      for (KeyValue kv : r.raw()) {
        System.out.print("rowkey : " + new String(kv.getRow()) + "  ");
        System.out.println(new String(kv.getFamily()) + ":"
            + new String(kv.getQualifier()) + " timestamp " + kv.getTimestamp()
            + new String(kv.getValue()));
      }
    }
  }

 
 public static void getAllData(HTable table) throws IOException {

    Scan s = new Scan();
    s.setMaxVersions();
    s.setBatch(1000);
    ResultScanner rs = table.getScanner(s);
    for (Result r : rs) {
      for (KeyValue kv : r.raw()) {
        System.out.print("rowkey : " + new String(kv.getRow()) + "  ");
        System.out.println(new String(kv.getFamily()) + ":"
            + new String(kv.getQualifier()) + " timestamp " + kv.getTimestamp()
            + new String(kv.getValue()));
      }
    }
  }

上面的函数即是一个简单的scan table的示例

调用的实现函数table.getScanner(s)

 
public ResultScanner getScanner(final Scan scan) throws IOException {
    ClientScanner s = new ClientScanner(scan);//new 一个ClientScanner对象
    s.initialize();// 初始化
    return s;
  }
 
public ResultScanner getScanner(final Scan scan) throws IOException {
    ClientScanner s = new ClientScanner(scan);//new 一个ClientScanner对象
    s.initialize();// 初始化
    return s;
  }

ClientScanner包含一个scan,以及caching。

客户端与regionserver通信一次,会找到regionserver的region,并扫描region返回一定数据。

这个数据量是由scan的Batch指定的。

而caching的作用就是通信一次找到region,调用扫描caching次,也就是说用这两个参数的话,一次通信可以返回的数据为caching*batch条。显然这会减少客户端和rs的通信量。

返回的数据时先放到客户端进行缓存的(这样也是为了提高性能)如果一次返回的数据过多的话,很有可能造成客户端的内存oom,通过batch的控制可以很好的解决这个问题。

clientScanner的初始化过程主要如下

private boolean nextScanner(int nbRows, final boolean done)
    throws IOException {
      // Close the previous scanner if it's open
      if (this.callable != null) {
        this.callable.setClose();
        getConnection().getRegionServerWithRetries(callable);
        this.callable = null;
      }
      // Where to start the next scanner
      byte [] localStartKey;
      // if we're at end of table, close and return false to stop iterating
      if (this.currentRegion != null) {
        byte [] endKey = this.currentRegion.getEndKey();
        if (endKey == null ||
            Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY) ||
            checkScanStopRow(endKey) ||
            done) {
          close();
                   return false;
        }
        localStartKey = endKey;
             } else {
        localStartKey = this.scan.getStartRow();
      }
           try {
        callable = getScannerCallable(localStartKey, nbRows);
        // Open a scanner on the region server starting at the
        // beginning of the region
        getConnection().getRegionServerWithRetries(callable);
        this.currentRegion = callable.getHRegionInfo();
      } catch (IOException e) {
        close();
        throw e;
      }
      return true;
    }

第一步是关闭callable,关闭的原因,我们下面会分析到

第二步确定localstartkey,当只有一个region的时候localstartkey是不用变化的,因为client是通过startkey来确定region的位置的。但是当有多个region的时候startkey就会变化。startkey是scan中的statrow,我们前面说过hbase是以rowkey作为索引的,也就是此处的startkey,通过tablename以及startrow我们可以知道第一个region的位置。

第三步就是getConnection().getRegionServerWithRetries(callable);
    这个过程包含两步:

    1.找到regionserver,并与之建立通信

    2 .如果是第一次扫描一个region的话会rpc调用regionserver的openscanner方法,构造一个Regionscanner,以后的数据查找都是使用该regionscanner来实现的,其内部结构比较复杂具体可见;然后会随机生成一个scannerId,与regionscanner一起放入map中,并把id返回给客户端。客户端拿到这个id以后,会与rs进行通信,rpc调用regionserver的next()方法,第一步就是会从map中取出其对应的regionscanner,类似一个tcp的三次握手,具体的取数据过程就是由regionscanner来负责的。

   

clientscanner重写了迭代器:

主要的一些功能是:当扫描的数据为空了,注意这里是指数据为空,并非是null,当初看代码的时候在此处纠结了很长时间。数据为空的时候,此时就认为该region已经扫描结束了,需要找第二个region并构造其regionscanner(region是有序的,以其在meta表中的顺序依次扫描)也就是调用nextscanner()方法。

如果这个时候callable没有关闭的话,那么regionscanner就不会清除。regionscanner中维护的是一系列storeScanner,每个storescanner下面又有很多storefilescanner,这些会消耗很多内存。

此外构建新的nextscanner的时候会重置startkey,以寻找下一个region。

依次循环知道到达endkey位置 。。

本文主要分析的是客户端的机制,涉及到服务器端的都是一笔带过。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值