華為方案
華為在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); } }