hbase filter类

在HBase中,我们可以利用其Scan接口对数据进行扫描,具体方式如下, 
Java代码   收藏代码
  1. Scan scanConfig = new Scan();  
  2. ResultScanner scanner = table.getScanner(scanConfig);  
  3. for (Result result : scanner) {//ResultScanner实现了Iterator接口  
  4.     //do something here  
  5. }  

在扫描过程中,我们想要哪些数据返回哪些数据不返回是用过Scan这个类提供的各种方法来控制的,下面列出了一些主要的方法, 
Java代码   收藏代码
  1. /** 
  2. * 返回指定列族下的指定qualifier中的值 
  3. */  
  4. public Scan addColumn(byte[] family, byte[] qualifier)  
  5. /** 
  6. * 设置扫描的起始行,starRow是存储时候用的rowKey 
  7. */  
  8. public Scan setStartRow(byte[] startRow)  
  9. /** 
  10. * 设置扫描的结束行,stopRow是存储时候用的rowKey 
  11. */  
  12. public Scan setStopRow(byte[] stopRow)  
  13. /** 
  14. * 设置返回结果的时间戳 
  15. */  
  16. public Scan setTimeStamp(long timestamp)  
  17. /** 
  18. * 设置返回结果的时间戳返回 
  19. */  
  20. public Scan setTimeRange(long minStamp, long maxStamp)  
  21. /** 
  22. * 设置过滤器,这是非常灵活的扫描机制 
  23. */  
  24. 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()方法如下, 
Java代码   收藏代码
  1. protected long openScanner() throws IOException {  
  2.     return this.server.openScanner(this.location.getRegionInfo().getRegionName(),  
  3.       this.scan);  
  4.   }  

其中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方法中的,其代码如下, 
Java代码   收藏代码
  1. protected void sendParam(Call call) {  
  2.       if (shouldCloseConnection.get()) {  
  3.         return;  
  4.       }  
  5.   
  6.       DataOutputBuffer d=null;  
  7.       try {  
  8.         //noinspection SynchronizeOnNonFinalField  
  9.         synchronized (this.out) { // FindBugs IS2_INCONSISTENT_SYNC  
  10.           if (LOG.isDebugEnabled())  
  11.             LOG.debug(getName() + " sending #" + call.id);  
  12.   
  13.           //for serializing the  
  14.           //data to be written  
  15.           d = new DataOutputBuffer();  
  16.           d.writeInt(0xdeadbeef); // placeholder for data length  
  17.           d.writeInt(call.id);  
  18.           call.param.write(d);  
  19.           byte[] data = d.getData();  
  20.           int dataLength = d.getLength();  
  21.           // fill in the placeholder  
  22.           Bytes.putInt(data, 0, dataLength - 4);  
  23.           out.write(data, 0, dataLength);  
  24.           out.flush();  
  25.         }  
  26.       } catch(IOException e) {  
  27.         markClosed(e);  
  28.       } finally {  
  29.         //the buffer is just an in-memory buffer, but it is still polite to  
  30.         // close early  
  31.         IOUtils.closeStream(d);  
  32.       }  
  33.     }  

我们会发现这个方法会调用Call对象中的param属性的write方法,目的是让param属性自己将自己转换成字节,然后放入DataOutputBuffer里面。在ScannerCallable.openScanner()方法中,这个param就是Scan对象,通过查看Scan对象的源代码,我们发现这个对象实现了HBase的Writable接口,因此确实有一个write方法,这个方法的代码如下, 
Java代码   收藏代码
  1. public void write(final DataOutput out)  
  2.   throws IOException {  
  3.     out.writeByte(SCAN_VERSION);  
  4.     Bytes.writeByteArray(out, this.startRow);  
  5.     Bytes.writeByteArray(out, this.stopRow);  
  6.     out.writeInt(this.maxVersions);  
  7.     out.writeInt(this.batch);  
  8.     out.writeInt(this.caching);  
  9.     out.writeBoolean(this.cacheBlocks);  
  10.     if(this.filter == null) {  
  11.       out.writeBoolean(false);  
  12.     } else {  
  13.       out.writeBoolean(true);  
  14.       Bytes.writeByteArray(out, Bytes.toBytes(filter.getClass().getName()));  
  15.       filter.write(out);  
  16.     }  
  17.     tr.write(out);  
  18.     out.writeInt(familyMap.size());  
  19.     for(Map.Entry<byte [], NavigableSet<byte []>> entry : familyMap.entrySet()) {  
  20.       Bytes.writeByteArray(out, entry.getKey());  
  21.       NavigableSet<byte []> columnSet = entry.getValue();  
  22.       if(columnSet != null){  
  23.         out.writeInt(columnSet.size());  
  24.         for(byte [] qualifier : columnSet) {  
  25.           Bytes.writeByteArray(out, qualifier);  
  26.         }  
  27.       } else {  
  28.         out.writeInt(0);  
  29.       }  
  30.     }  
  31.   }  


这个方法会把Scan对象中的所有在RegionServer执行扫描过程中需要用到的参数全部写入DataOutput中,当然也包括Filter。从这个代码中,我们很容易地知道HBase中的Filter也是实现了Writable接口的。而且在写入Filter对象本身的byte之前,还会往输出流中写入这个Filter的类名,这一步是必须的,否则RegionServer将无法知道它们需要使用哪一个Filter。正式由于这一点,给用户自定义Filter带来了限制。由于真正的扫描数据的过程是在RegionServer上发生的,HBase就采用这种序列化的方式将扫描数据需要用到的类和属性告知RegionServer。对于用户自定义的Filter,由于在RegionServer上是找不到相关类的,因此在执行的过程中会抛出异常。也许有人会说,把自定义的Filter放到各个RegionServer上去不就可以了吗?这样做确实可以,但是维护成本会比较大,而且自定义Filter有时候也不是那么好些的,因为会设置到序列化自身。因此我们还是应该使用HBase已经提供的Filter,如果能够合理有效地组合预定义的各个Filter,是足够用来实现我们的查询需求的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值