`
Mysun
  • 浏览: 270485 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

利用Filter进行HBase查询

阅读更多
在HBase中,我们可以利用其Scan接口对数据进行扫描,具体方式如下,
Scan scanConfig = new Scan();
ResultScanner scanner = table.getScanner(scanConfig);
for (Result result : scanner) {//ResultScanner实现了Iterator接口
	//do something here
}

在扫描过程中,我们想要哪些数据返回哪些数据不返回是用过Scan这个类提供的各种方法来控制的,下面列出了一些主要的方法,
/**
* 返回指定列族下的指定qualifier中的值
*/
public Scan addColumn(byte[] family, byte[] qualifier)
/**
* 设置扫描的起始行,starRow是存储时候用的rowKey
*/
public Scan setStartRow(byte[] startRow)
/**
* 设置扫描的结束行,stopRow是存储时候用的rowKey
*/
public Scan setStopRow(byte[] stopRow)
/**
* 设置返回结果的时间戳
*/
public Scan setTimeStamp(long timestamp)
/**
* 设置返回结果的时间戳返回
*/
public Scan setTimeRange(long minStamp, long maxStamp)
/**
* 设置过滤器,这是非常灵活的扫描机制
*/
public Scan setFilter(Filter filter)

其中最为有用的是setFilter方法,这个方法允许我们为扫描过程设置一个过滤器,从而过滤掉那些不符合要求的记录。Filter机制非常灵活,基本可以满足我们所有的查询需求。HBase的类库中已经预先定义了很多的Filter,通过使用这些预定义的Filter,我们可以非常灵活地组装自己的查询需求。但是依赖于Hbase对Filter的实现机制,还是会存在一些限制,这个下面马上就会说到。
HBase是如何实现Filter的
HBase提供了一个Java实现的客户端,用来与服务器进行通讯。这个客户端的通讯层使用了RPC来进行封装,封装所有RPC操作的是org.apache.hadoop.hbase.ipc.HBaseRPC,而这个类会将真正的通讯委托给org.apache.hadoop.hbase.ipc.HBaseClient类来执行。如果跟踪开头给出的代码中的第二行代码的执行,会发现其执行流程是这样的,

上图中的ScannerCallable.openScanner()方法如下,
protected long openScanner() throws IOException {
    return this.server.openScanner(this.location.getRegionInfo().getRegionName(),
      this.scan);
  }

其中server属性的类型是HRegionInterface,从调用栈可以看到这个接口被动态代理掉了,最终的调用会委托给HBaseRPC执行,也就是我们看到的调用栈最顶端的那行代码。检查HRegionInterface接口提供的方法,会发现其主要是用来与HBase的RegionServer进行交互的(HBase的RegionServer就是真正存放数据的服务器)。在HBaseRPC接受openScanner这个方法调用之后,它会委托HBaseClient去与各个RegionServer进行通信,告诉它们有个客户端 正在发起openScanner方法调用,同时会把openScanner方法的参数序列化之后传给各个RegionServer。这个方法调用成功之后(HBase客户端收到了所有RegionServer的正确相应),openScanner方法就返回了。
如果本文开头代码所示,第三行之后的代码就开始从扫描器中拿结果了。这个时候HBase客户端会逐个与RegionServer进行通信,告诉它们开始扫描吧。由于在openScanner的时候,已经把参数传递给了各个RegionServer,各个RegionServer就可以根据参数来执行扫描了,然后将扫描结果返回给HBase客户端,然后客户端程序就可以拿到结果进行处理了。
HBase中的Filter有何限制

从ScannerCallable.openScanner的参数可以看到,HBase客户端其实是把客户端程序中创建的Scan对象当作参数传递给通讯层的,也就是说这个Scan参数会被序列化给各个RegionServer,当然也就包括设置在其中的Filter。
继续跟踪代码会发现,对参数执行序列化操作的代码是放在HBaseClient的一个叫做Connection的内部类中的sendParam方法中的,其代码如下,
protected void sendParam(Call call) {
      if (shouldCloseConnection.get()) {
        return;
      }

      DataOutputBuffer d=null;
      try {
        //noinspection SynchronizeOnNonFinalField
        synchronized (this.out) { // FindBugs IS2_INCONSISTENT_SYNC
          if (LOG.isDebugEnabled())
            LOG.debug(getName() + " sending #" + call.id);

          //for serializing the
          //data to be written
          d = new DataOutputBuffer();
          d.writeInt(0xdeadbeef); // placeholder for data length
          d.writeInt(call.id);
          call.param.write(d);
          byte[] data = d.getData();
          int dataLength = d.getLength();
          // fill in the placeholder
          Bytes.putInt(data, 0, dataLength - 4);
          out.write(data, 0, dataLength);
          out.flush();
        }
      } catch(IOException e) {
        markClosed(e);
      } finally {
        //the buffer is just an in-memory buffer, but it is still polite to
        // close early
        IOUtils.closeStream(d);
      }
    }

我们会发现这个方法会调用Call对象中的param属性的write方法,目的是让param属性自己将自己转换成字节,然后放入DataOutputBuffer里面。在ScannerCallable.openScanner()方法中,这个param就是Scan对象,通过查看Scan对象的源代码,我们发现这个对象实现了HBase的Writable接口,因此确实有一个write方法,这个方法的代码如下,
public void write(final DataOutput out)
  throws IOException {
    out.writeByte(SCAN_VERSION);
    Bytes.writeByteArray(out, this.startRow);
    Bytes.writeByteArray(out, this.stopRow);
    out.writeInt(this.maxVersions);
    out.writeInt(this.batch);
    out.writeInt(this.caching);
    out.writeBoolean(this.cacheBlocks);
    if(this.filter == null) {
      out.writeBoolean(false);
    } else {
      out.writeBoolean(true);
      Bytes.writeByteArray(out, Bytes.toBytes(filter.getClass().getName()));
      filter.write(out);
    }
    tr.write(out);
    out.writeInt(familyMap.size());
    for(Map.Entry<byte [], NavigableSet<byte []>> entry : familyMap.entrySet()) {
      Bytes.writeByteArray(out, entry.getKey());
      NavigableSet<byte []> columnSet = entry.getValue();
      if(columnSet != null){
        out.writeInt(columnSet.size());
        for(byte [] qualifier : columnSet) {
          Bytes.writeByteArray(out, qualifier);
        }
      } else {
        out.writeInt(0);
      }
    }
  }


这个方法会把Scan对象中的所有在RegionServer执行扫描过程中需要用到的参数全部写入DataOutput中,当然也包括Filter。从这个代码中,我们很容易地知道HBase中的Filter也是实现了Writable接口的。而且在写入Filter对象本身的byte之前,还会往输出流中写入这个Filter的类名,这一步是必须的,否则RegionServer将无法知道它们需要使用哪一个Filter。正式由于这一点,给用户自定义Filter带来了限制。由于真正的扫描数据的过程是在RegionServer上发生的,HBase就采用这种序列化的方式将扫描数据需要用到的类和属性告知RegionServer。对于用户自定义的Filter,由于在RegionServer上是找不到相关类的,因此在执行的过程中会抛出异常。也许有人会说,把自定义的Filter放到各个RegionServer上去不就可以了吗?这样做确实可以,但是维护成本会比较大,而且自定义Filter有时候也不是那么好些的,因为会设置到序列化自身。因此我们还是应该使用HBase已经提供的Filter,如果能够合理有效地组合预定义的各个Filter,是足够用来实现我们的查询需求的。
  • 大小: 211.4 KB
分享到:
评论
3 楼 wubo2qml 2013-11-22  
问下如何进行列中数字大小的比较。我看了几个过滤器,最接近的是
SingleColumnValueFilter  但是这个里面的过滤器是按照字典顺序进行比较的!
2 楼 blackproof 2013-08-12  
rowkey是A_B
我知道要的A集合,也知道要的B范围
不用自定义,怎么Filter呢
1 楼 bin_1715575332 2013-05-06  
文章不错,尤其了后半部分讲解一些原理。

相关推荐

Global site tag (gtag.js) - Google Analytics