HBase 多级索引
华为方案
华为在HBTC 2012上由其高级技术经理Anoop Sam John透露了其二级索引方案,这在业界引起极大的反响,甚至有人认为,如果华为早点公布这个方案,hbase的某些问题早就解决了。其核心思想是保证索引表和主表在同一个region server上。
更新:目前该方案华为已经开源,详见:https://github.com/Huawei-Hadoop/hindex
下面来对其方案做一个分析。
1.整体架构
这个架构在Client Ext中设定索引细节,在Balancer中收集信息,在Coprocessor中管理二级索引数据。
2.表创建
在创建表的时候,在同一个region server上创建索引表,且一一对应。
3.插入操作
在主表中插入某条数据后,用Coprocessor将索引列写到索引表中去,写道索引表中的数据的主键为:region开始key+索引名+索引列值+主表row key。这么做,是为了让其在同一个分布规则下,索引表会跟主表在通过region server上,在查询的时候就可以少一次rpc。
4.scan操作
一个查询到来的时候,通过coprocessor钩子,先从索引表中查询范围row,然后再从主表中相关row中扫描获得最终数据。
5. split操作处理
为了使主表和索引表在同一个RS上,要禁用索引表的自动和手动split,只能由主表split的时候触发,当主表split的时候,对索引表按其对应数据进行划分,同时,对索引表的第二个daughter split的row key的前面部分修改为对应的主键的row key。
6. 性能
查询性能极大提升,插入性能下降10%左右
总结,本文对华为hbase使用coprocessor进行二级索引的方案的创建表,插入数据,查询数据的步骤进行了一个粗略分析,以窥其全貌。在使用的时候,可以作为一个参考。
该方案主要优势
1. Idx表的索引所处位置与原数据位置处于同一个Region内
2. 使用RegionObserver钩子,减少了IPC次数
HBase官方文档:
http://hbase.apache.org/book.html#cp
https://blogs.apache.org/hbase/entry/coprocessor_introduction
相关部署:
HBase官方example : alter 't1', METHOD => 'table_att', 'coprocessor'=>'hdfs:///foo.jar|com.foo.FooRegionObserver|1001|arg1=1,arg2=2' 上传jar包到hdfs: bin/hdfs dfs -put ~/bzhou/wad-hbase-0.0.1-SNAPSHOT.jar /data/weidou_ad/wad-hbase.jar 上传后的位置: hdfs://wdc0:9000/data/weidou_ad/wad-hbase.jar 修改HBase表: alter 'TestCoprocessor', METHOD => 'table_att', 'coprocessor'=>'hdfs://wdc0:9000/data/weidou_ad/wad-hbase.jar|com.weidou.wad.hbase.WadBeforeScan||'
相关开发:
class XXXXX implements RegionObserver
public class WadBeforeScan extends BaseRegionObserver { Log log = LogFactory.getLog(WadBeforeScan.class); @Override public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get, List<Cell> results) throws IOException { super.preGetOp(e, get, results); } @Override public boolean preScannerNext(ObserverContext<RegionCoprocessorEnvironment> e, InternalScanner s, List<Result> results, int limit, boolean hasMore) throws IOException { log.info("preScannerNext.limit:" + limit); log.info("preScannerNext.hasMore:" + hasMore); log.info("preScannerNext.result.size:" + ((results == null) ? 0 : results.size())); // if (results != null) { // int idx = 0; // for (Result r : results) { // log.info("preScannerNext.result." + (++idx) + ":" + r.toString()); // } // } // // HTableInterface itf = e.getEnvironment().getTable(TableName.valueOf("BidRequest")); // // Scan scan = new Scan(); // byte[] startRow = Bytes.toBytes("0_00035792-a7ea-478e-8c7d-4ce9015fe7e9"); // byte[] endRow = Bytes.toBytes("0_00035792-a7ea-478e-8c7d-4ce9015fe7e9"); // scan.setStartRow(startRow); // scan.setStartRow(endRow); // // ResultScanner resultscanner = itf.getScanner(scan); // for (Result result : resultscanner) { // results.add(result); // } String rowkey1 = "testrowkey1"; String family1 = "testfamily1"; String column1 = "testcol1"; String value1 = "testvalue1"; Cell cell = new KeyValue(Bytes.toBytes(rowkey1), Bytes.toBytes(family1), Bytes.toBytes(column1), Bytes.toBytes(value1)); List<Cell> cells = Lists.newArrayList(); cells.add(cell); Result r = Result.create(cells); results.add(r); return super.preScannerNext(e, s, results, limit, hasMore); } }