標籤:hbase coprocessor 擴充
Coprocessor是HBase 0.92.0引入的特性。使用Coprocessor,可以將一些計算邏輯下推到HBase節點,HBase由一個單純的儲存系統升級為分布式資料處理平台。
Coprocessor分為兩種:Observer和Endpoint。Observer能修改擴充已有的用戶端操作功能,而Endpoint能引入新的用戶端操作。
Observer
Observer的作用類似於資料庫的觸發器或者AOP中的advice。為Put操作增加Observer,其中1-2-4-6是一次正常的Put操作RPC調用過程,而3和5屬於Observer,可以在Put操作之前和之後加入自訂處理邏輯。
Observer包括三種,RegionObserver(針對資料訪問和更新操作,運行在Region上)/WALObserver(針對WAL日誌事件,運行在RegionServer上下文)/MasterObserver(針對DDL操作,運行在Master節點)。
Endpoint
Endpoint的作用則類似於資料庫預存程序。實現機制是通過擴充HBase RPC協議,給用戶端暴露新的操作介面。如,用戶端負責發起調用和收集結果,服務端各節點負責並行計算。
實戰
以上一章的follows表為例,通過Observer實現followedBy被關注表資料一致性維護,Endpoint實現關注人數量統計。
因為要實現在插入follows表時自動插入followedBy表,需要用到關注人/被關注人使用者名稱資訊,所以首先升級schema。
實現Observer
代碼中有三處注釋值得注意:
- postPut方法在put操作之後被調用。
- 如果通過hbase-site.xml安裝Observer,會應用到全域所有表,所以這裡判斷put操作的是否follows表。
- 這裡有點bad smell。Observer運行在伺服器端,為了共用代碼,又調用用戶端代碼,僅為示範作用。
packageHBaseIA.TwitBase.coprocessors;//…publicclass FollowsObserver extends BaseRegionObserver { private HTablePool pool = null; @Override public void start(CoprocessorEnvironment env)throws IOException { pool = newHTablePool(env.getConfiguration(), Integer.MAX_VALUE); } @Override public void stop(CoprocessorEnvironment env)throws IOException { pool.close(); } @Override public void postPut(//1,在Put操作之後調用 finalObserverContext<RegionCoprocessorEnvironment> e, final Put put, final WALEdit edit, final boolean writeToWAL) throws IOException { byte[] table=e.getEnvironment().getRegion().getRegionInfo().getTableName(); if (!Bytes.equals(table,FOLLOWS_TABLE_NAME)) return; //2,判斷表名 KeyValue kv =put.get(RELATION_FAM, FROM).get(0); String from =Bytes.toString(kv.getValue()); kv = put.get(RELATION_FAM,TO).get(0); String to =Bytes.toString(kv.getValue()); RelationsDAO relations = newRelationsDAO(pool); relations.addFollowedBy(to,from);//3,插入followedBy表 }}Observer的安裝可以通過修改hbase-site.xml或者使用tableschema修改陳述式完成,前者需要重啟HBase服務,後者只需要重新上下線對應表。
$ hbase shellHBaseShell; enter 'help<RETURN>' for list of supported commands.Type"exit<RETURN>" to leave the HBase ShellVersion0.92.0, r1231986, Mon Jan 16 13:16:35 UTC 2012hbase(main):001:0>disable 'follows'0 row(s) in 7.0560 secondshbase(main):002:0>alter 'follows', METHOD => 'table_att','coprocessor'=>'file:///Users/ndimiduk/repos/hbaseiatwitbase/target/twitbase-1.0.0.jar|HBaseIA.TwitBase.coprocessors.FollowsObserver|1001|'Updatingall regions with the new schema...1/1regions updated.Done.0 row(s) in 1.0770 secondshbase(main):003:0>enable 'follows'0 row(s) in 2.0760 seconds
其中1001為優先順序,當載入多個Observer時,按照優先順序次序運行。
實現Endpoint
關注人數量統計可以通過用戶端Scan實現,相比Endpoint方案,有兩個待改進點:
- 傳輸所有關注人到用戶端,不必要的網路I/O。
- 拿到所有關注人Result結果後,遍曆實現計數是單線程的。
實現Endpoint包括三部分
定義PRC介面
publicinterface RelationCountProtocol extends CoprocessorProtocol { public long followedByCount(String userId) throwsIOException;}服務端實現
和用戶端不同,InternalScanner運行在特定Region上,返回的是原始的KeyValue對象。
packageHBaseIA.TwitBase.coprocessors;//…publicclass RelationCountImpl extends BaseEndpointCoprocessor implementsRelationCountProtocol { @Override public longfollowedByCount(String userId) throws IOException { byte[]startkey = Md5Utils.md5sum(userId); Scan scan = newScan(startkey); scan.setFilter(newPrefixFilter(startkey)); scan.addColumn(RELATION_FAM,FROM); scan.setMaxVersions(1); RegionCoprocessorEnvironmentenv= (RegionCoprocessorEnvironment)getEnvironment(); InternalScanner scanner =env.getRegion().getScanner(scan);//1,伺服器端 long sum = 0; List<KeyValue> results= new ArrayList<KeyValue>(); boolean hasMore = false; do { hasMore =scanner.next(results); sum += results.size(); results.clear(); } while (hasMore); scanner.close(); return sum; }}用戶端代碼
參考注釋:
- 定義Call執行個體
- 調用服務端Endpoint。
- 彙總所有RegionServer得到的結果
public long followedByCount (final String userId) throws Throwable { HTableInterface followed =pool.getTable(FOLLOWED_TABLE_NAME); final byte[] startKey = Md5Utils.md5sum(userId); final byte[] endKey =Arrays.copyOf(startKey, startKey.length); endKey[endKey.length-1]++; Batch.Call<RelationCountProtocol,Long> callable = newBatch.Call<RelationCountProtocol, Long>() { @Override public Longcall(RelationCountProtocol instance) throws IOException { returninstance.followedByCount(userId); } };//1 call instance Map<byte[], Long>results = followed.coprocessorExec( RelationCountProtocol.class, startKey, endKey, callable);//2 invoke endpoint long sum = 0; for(Map.Entry<byte[],Long> e : results.entrySet()) { sum +=e.getValue().longValue(); }//3 aggreagte results return sum;}Endpoint只能通過設定檔部署,還需要將相關jar包加入到HBase classpath。
<property> <name>hbase.coprocessor.region.classes</name> <value>HBaseIA.TwitBase.coprocessors.RelationCountImpl</value></property>