HBase協處理器(1.2官方文檔)

HBase Coprocessor

HBase Coprocessor是根據Google BigTable的coprocessor實現來建模的。

 

coprocessor框架提供了在管理數據的RegionServer上直接運行定製代碼的機制。我們正在努力消除HBase的實現和BigTable的架構之間的差距。

 

資源鏈接:

1.    Mingjie Lai’s blogpost Coprocessor Introduction.

2.    Gaurav Bhardwaj’sblog post The How To Of HBase Coprocessors.

警告:

Coprocessor是HBase的一個高級特性,僅供系統開發人員使用。由於coprocessor代碼直接在RegionServer上運行,並且可以直接訪問您的數據,因此它們引入了數據損壞、中間攻擊或其他惡意數據訪問的風險。目前,儘管在hbase-4047上已經進行了工作,但是還沒有機制可以防止Coprocessor的數據損壞。 此外,由於沒有資源隔離,一個善意但行爲不當的協處理器會嚴重降低集羣性能和穩定性。

協處理器概述

在HBase中使用Get或Scan來獲取數據,而在RDBMS中使用SQL查詢;爲了只獲取相關數據,在HBase中使用HBase Filter,而在RDBMS中使用Where子句。

 

在獲取數據之後,將對其進行計算。這種模式適用於幾千行和幾列的“小數據”。但是,當擴展到數十億行和數百萬列時,在網絡中移動大量的數據將會在網絡層造成瓶頸,並且客戶端需要足夠強大,並且有足夠的內存來處理大量的數據和計算。此外,客戶端代碼會變得更大、更復雜。

 

在這個場景中,協處理器可能更有意義。你可以將業務計算代碼放入一個RegionServer上運行的協處理器,在與數據相同的位置,並將結果返回給客戶端。

 

這只是使用協處理器更好的其中一個場景,以下的一些類比來解釋協處理器的好處。

 

 

Coprocessor類比

觸發器和存儲過程

 Observercoprocessor類似於RDBMS中的觸發器,它在特定事件(例如Get或Put)發生之前或之後執行你的代碼。

 

endpoint coprocessor與RDBMS中的存儲過程類似。因爲它允許你在RegionServer本身的數據執行自定義計算,而不是在客戶端。

 

MapReduce

MapReduce的工作原理是將計算移動到數據的位置。coprocon的操作方法是相同的。

 

AOP

如果你瞭解面向切面編程,可以將一個coprocessor看作是通過在將請求傳遞到最終目的地之前(或者甚至更改目的地) 攔截一個請求,然後運行一些自定義代碼的應用切面。

 

 

協處理器實現概述

1.     你的類需要繼承一個Coprocessor類,比如BaseRegionObserver,或者實現Coprocessor or CoprocessorService接口。

2.     使用HBase Shell,靜態地(從配置中)或動態地加載coprocessor。下文會提到。

3.     從客戶端代碼調用coprocessor。HBase可以有效地處理協處理器

 

查看API:

coprocessor

 

 

 

 

 

協處理器的類型

Observer Coprocessors

特定事件發生之前或者之後會觸發Observer coprocessors,在事件之前發生的Observer使用的方法是以pre爲前綴的,比如prePut 。在事件之後發生的Observer使用的方法是以post爲前綴的,比如postPut。

 

Observer Coprocessors的用例

Security

在執行Get或Put操作之前,你可以使用preGet或prePut方法檢查權限

Referential Integrity

HBase不直接支持RDBMS概念的引用完整性,也稱爲外鍵。可以使用協處理器來執行這種完整性,例如,如果你有一個規則,在每次往user表中插入數據時都必須遵循user_daily_attendance表中相應的條目,就可以實現Coprocessors接口在user表上使用prePut方法插入一條信息到user_daily_attendance

Secondary Indexes

您可以使用協處理器來維護二級索引

 

Observer Coprocessor的類型

RegionObserver

 

RegionObserver Coprocessor允許你該觀察一個region的事件,比如Get和Put操作

 

RegionServerObserver

RegionServerObserver Coprocessor可以觀察到關於regionserver的操作。例如啓動、停止或執行合併、提交或回滾。

MasterOvserver

可以觀察到HBase Master的操作。例如表創建、刪除或schema修改

WalObserver

可以觀察到寫入WAL的事件。

 

 Endpoint Coprocessor

EndpointCoprocessor 可以在數據的位置執行計算。

例如,需要計算整個表的運行平均值或總和,該表跨越了數百個region

在observer coprocessors約定中,代碼的運行是透明的。endpoint coprocessors必須顯式地調用TableHTableInterface, or HTable中的 CoprocessorService()方法。

 

加載協處理器

確保你的協處理器可用,並且可以被加載,無論是靜態加載和動態加載。

 

靜態加載

按照以下步驟來靜態加載您的coprocessor。請記住,必須重新啓動HBase以卸載已被靜態加載的coprocessor。

1.hbase-site.xml中配置屬性:

通過hbase.coprocessor.region.classes 配置 RegionObservers 和 Endpoints.

通過hbase.coprocessor.wal.classes 配置 WALObservers.

通過hbase.coprocessor.master.classes 配置MasterObservers.

<value>必須寫類的全名。

例子:

<property>

    <name>hbase.coprocessor.region.classes</name>

    <value>org.myname.hbase.coprocessor.endpoint.SumEndPoint</value>

</property>

 

如果加載多各類,需要用逗號分隔,該框架使用默認的類加載器來加載配置的類,所以這些jar包必須在HBase的classpath中。

以這種方式加載的協處理器將在所有表的所有region中活動。這些也被稱爲系統協處理器。

列表中的第一個協處理器將會分配一個優先級Coprocessor.Priority.SYSTEM,之後的遞增加1。當調用註冊的觀察者時,框架會按照優先順序執行它們的回調方法。

2.將jar包放入HBase的lib目錄下

3.重啓HBase

 

靜態卸載

1.     在hbase-site.xml中刪除配置。

2.     重啓HBase

3.     刪除jar包

 

動態加載

動態加載協處理器不用重啓HBase,也被稱爲表協處理器。

此外,動態加載coprocessor會作爲表上的schema更改,並且必須將表脫機以加載coprocessor。

以下是三種方法:

 

1.   使用HBase Shell

1.   hbase> disable 'users'
 
2.   hbase alter 'users', METHOD => 'table_att', 'Coprocessor'=>'hdfs://<namenode>:<port>/
user/<hadoop-user>/coprocessor.jar| org.myname.hbase.Coprocessor.RegionObserverExample|1073741823|
arg1=1,arg2=2'
     

Coprocessor框架將嘗試從Coprocessor表屬性值中讀取類信息。該值包含四個由管道()字符分隔的信息

文件路徑:Coprocessor的jar包必須位於所有regionServer都可以讀到的位置。

類名:協處理器的完整類名。

優先級:該框架將確定在同一個鉤子上註冊的所有配置觀察器的執行順序,並使用優先級。這個字段可以留空。在這種情況下,框架將分配默認的優先級值。

參數:協處理器需要的參數。

 
3.   hbase(main):003:0> enable 'users'
 
4. hbase(main):04:0> describe 'users'

coprocessor應該在table屬性中列出。

 

2.   使用API (所有HBase版本通用)

下面的Java代碼展示瞭如何使用HTableDescriptor的setValue()方法在users表上加載一個coprocessor。

 

TableName tableName = TableName.valueOf("users");
String path = "hdfs://<namenode>:<port>/user/<hadoop-user>/coprocessor.jar";
Configuration conf = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(conf);
Admin admin = connection.getAdmin();
admin.disableTable(tableName);
HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet");
columnFamily1.setMaxVersions(3);
hTableDescriptor.addFamily(columnFamily1);
HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet");
columnFamily2.setMaxVersions(3);
hTableDescriptor.addFamily(columnFamily2);
hTableDescriptor.setValue("COPROCESSOR$1", path + "|"
+ RegionObserverExample.class.getCanonicalName() + "|"
+ Coprocessor.PRIORITY_USER);
admin.modifyTable(tableName, hTableDescriptor);
admin.enableTable(tableName);
 
 

3. 使用API0.96+

在HBase 0.96和更新版本中,HTableDescriptor的addCoprocessor()方法提供了一種更方便的方式來動態加載coprocessor。

 

 

 

 

 

TableName tableName = TableName.valueOf("users");
String path = "hdfs://<namenode>:<port>/user/<hadoop-user>/coprocessor.jar";
Configuration conf = HBaseConfiguration.create();
HBaseAdmin admin = new HBaseAdmin(conf);
admin.disableTable(tableName);
HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet");
columnFamily1.setMaxVersions(3);
hTableDescriptor.addFamily(columnFamily1);
HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet");
columnFamily2.setMaxVersions(3);
hTableDescriptor.addFamily(columnFamily2);
hTableDescriptor.addCoprocessor(RegionObserverExample.class.getCanonicalName(), path,
Coprocessor.PRIORITY_USER, null);
admin.modifyTable(tableName, hTableDescriptor);
admin.enableTable(tableName);

 

 

動態卸載

1.   使用HBase Shell

1. hbase> disable 'users'
2.  hbase> alter 'users', METHOD => 'table_att_unset', NAME => 'coprocessor$1'
3.  hbase> enable 'users'
 

2.   使用API

通過使用setValue()或addCoprocessor()方法重新加載表定義,而無需設置coprocessor的值。這將刪除連接到表上的所有協處理器。

 

TableName tableName = TableName.valueOf("users");
String path = "hdfs://<namenode>:<port>/user/<hadoop-user>/coprocessor.jar";
Configuration conf = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(conf);
Admin admin = connection.getAdmin();
admin.disableTable(tableName);
HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet");
columnFamily1.setMaxVersions(3);
hTableDescriptor.addFamily(columnFamily1);
HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet");
columnFamily2.setMaxVersions(3);
hTableDescriptor.addFamily(columnFamily2);
admin.modifyTable(tableName, hTableDescriptor);
admin.enableTable(tableName);
 
 
0.96+版本中可以使用 HTableDescriptor類的removeCoprocessor()方法。
 
 

案例

這些示例假設有一個名爲users的表,該表有兩個列族,包含個人和薪水的詳細信息。下面是users表的圖形表示。

Observer Example

下面這個Observer 協處理器阻止了用戶admin從Get和Scan操作中返回。

 

 

Step1:

寫一個類繼承 BaseRegionObserver類。

Step2:

       重寫preGetOp()方法(棄用preGet()方法)來檢查客戶端是否查詢了帶有值admin的rowkey。如果是。返回一個空結果集,如果不是,正常處理該請求。

      

Step3:

       打Jar包

Step4:

       將jar包放到HDFS上,HBase可以找到的位置。

Step5:

       加載協處理器。

Step6:

       寫一個簡單的程序測試。

      

實現:

 

public class RegionObserverExample extends BaseRegionObserver {
 
    private static final byte[] ADMIN = Bytes.toBytes("admin");
    private static final byte[] COLUMN_FAMILY = Bytes.toBytes("details");
    private static final byte[] COLUMN = Bytes.toBytes("Admin_det");
    private static final byte[] VALUE = Bytes.toBytes("You can't see Admin details");
 
    @Override
    public void preGetOp(final ObserverContext e, final Get get, final List results)
    throws IOException {
 
        if (Bytes.equals(get.getRow(),ADMIN)) {
            Cell c = CellUtil.createCell(get.getRow(),COLUMN _FAMILY, COLUMN,
            System.currentTimeMillis(), (byte)4, VALUE);
            results.add(c);
            e.bypass();
        }
 
        List kvs = new ArrayList(results.size());
        for (Cell c : results) {
            kvs.add(KeyValueUtil.ensureKeyValue(c));
        }
        preGet(e, get, kvs);
        results.clear();
        results.addAll(kvs);
    }
}
 

 

重寫preGetOp()只是作用於Get操作,還需要重寫preScannerOpen()方法來在scan方法中過濾admin行。

 

@Override
public RegionScanner preScannerOpen(final ObserverContext e, final Scan scan,
final RegionScanner s) throws IOException {
 
    Filter filter = new RowFilter(CompareOp.NOT_EQUAL, new BinaryComparator(ADMIN));
    scan.setFilter(filter);
    return s;
}

 

方法可以生效,但是有一個副作用:如果客戶端在Scan時使用了一個過濾器,會被你自己的過濾器覆蓋。所以你可以顯式的刪除所以admin的結果來代替。

 

@Override
public boolean postScannerNext(final ObserverContext e, final InternalScanner s,
final List results, final int limit, final boolean hasMore) throws IOException {
        Result result = null;
    Iterator iterator = results.iterator();
    while (iterator.hasNext()) {
    result = iterator.next();
        if (Bytes.equals(result.getRow(), ROWKEY)) {
            iterator.remove();
            break;
        }
    }
    return hasMore;
}

 

 

Endpoint Example

仍然使用users表,這個示例實現了一個coprocessor來計算所有員工薪水的總和,使用一個endpoint協處理器。

 

Step1:

       創建一個.proto文件定義服務:

 

option java_package = "org.myname.hbase.coprocessor.autogenerated";
option java_outer_classname = "Sum";
option java_generic_services = true;
option java_generate_equals_and_hash = true;
option optimize_for = SPEED;
message SumRequest {
    required string family = 1;
    required string column = 2;
}
 
message SumResponse {
  required int64 sum = 1 [default = 0];
}
 
service SumService {
  rpc getSum(SumRequest)
    returns (SumResponse);
}

 

Step2:

       使用protoc命令用.proto文件生成java代碼

      

$ mkdir src
$ protoc --java_out=src ./sum.proto
Step3:

       寫一個類繼承生成的service類並實現Coprocessor接口和CoprocessorService接口,重寫service方法:

 

        public class SumEndPoint extends SumService implements Coprocessor, CoprocessorService {
 
    private RegionCoprocessorEnvironment env;
 
    @Override
    public Service getService() {
        return this;
    }
 
    @Override
    public void start(CoprocessorEnvironment env) throws IOException {
        if (env instanceof RegionCoprocessorEnvironment) {
            this.env = (RegionCoprocessorEnvironment)env;
        } else {
            throw new CoprocessorException("Must be loaded on a table region!");
        }
    }
 
    @Override
    public void stop(CoprocessorEnvironment env) throws IOException {
        // do mothing
    }
 
    @Override
    public void getSum(RpcController controller, SumRequest request, RpcCallback done) {
        Scan scan = new Scan();
        scan.addFamily(Bytes.toBytes(request.getFamily()));
        scan.addColumn(Bytes.toBytes(request.getFamily()), Bytes.toBytes(request.getColumn()));
        SumResponse response = null;
        InternalScanner scanner = null;
        try {
            scanner = env.getRegion().getScanner(scan);
            List results = new ArrayList();
            boolean hasMore = false;
                        long sum = 0L;
                do {
                        hasMore = scanner.next(results);
                        for (Cell cell : results) {
                            sum = sum + Bytes.toLong(CellUtil.cloneValue(cell));
                     }
                        results.clear();
                } while (hasMore);
 
                response = SumResponse.newBuilder().setSum(sum).build();
 
        } catch (IOException ioe) {
            ResponseConverter.setControllerException(controller, ioe);
        } finally {
            if (scanner != null) {
                try {
                    scanner.close();
                } catch (IOException ignored) {}
            }
        }
        done.run(response);
    }
}
Configuration conf = HBaseConfiguration.create();
// Use below code for HBase version 1.x.x or above.
Connection connection = ConnectionFactory.createConnection(conf);
TableName tableName = TableName.valueOf("users");
Table table = connection.getTable(tableName);
 
//Use below code HBase version 0.98.xx or below.
//HConnection connection = HConnectionManager.createConnection(conf);
//HTableInterface table = connection.getTable("users");
 
final SumRequest request = SumRequest.newBuilder().setFamily("salaryDet").setColumn("gross")
                            .build();
try {
Map<byte[], Long> results = table.CoprocessorService (SumService.class, null, null,
new Batch.Call<SumService, Long>() {
    @Override
        public Long call(SumService aggregate) throws IOException {
BlockingRpcCallback rpcCallback = new BlockingRpcCallback();
            aggregate.getSum(null, request, rpcCallback);
            SumResponse response = rpcCallback.get();
            return response.hasSum() ? response.getSum() : 0L;
        }
    });
    for (Long sum : results.values()) {
        System.out.println("Sum = " + sum);
    }
} catch (ServiceException e) {
e.printStackTrace();
} catch (Throwable e) {
    e.printStackTrace();
}

 

 

Step4:

加載協處理器

Step5:

       編寫客戶端代碼來調用協處理器

 

部署協處理器的指導方針

捆綁協處理器

您可以將一個coprocessor的所有類綁定到regionserver的類路徑上的單個JAR中,易於部署,另外,將所有依賴項放在regionserver的類路徑中,這樣它們可以在regionserver啓動時加載。regionserver的類路徑設置在regionserver的hbase-env.sh文件中。

 

自動部署

可以使用工具比如Puppet, Chef, 或 Ansible將該JAR文件發送到您的regionserver文件系統的所需位置,並重新啓動每個regionserver,實現自動部署。

 

更新協處理器

部署一個給定的coprocessor的新版本並不像禁用它、替換JAR並重新啓用協處理器那樣簡單。這是因爲除非刪除所有當前的引用,否則無法在JVM中重新加載類。由於當前JVM引用了現有的coprocessor,所以必須重新啓動JVM,以重新啓動regionserver,以替換它。這種行爲預計不會改變。

 

協處理器日誌

Coprocessor框架不提供超出標準Java日誌記錄的API。

 

協處理器配置

如果不希望從HBase Shell加載協處理器,可以在hbase-site.xml中配置參數:

 

<property>
  <name>arg1</name>
  <value>1</value>
</property>
<property>
  <name>arg2</name>
  <value>2</value>
</property>

 

可以用以下代碼讀取配置:

 

Configuration conf = HBaseConfiguration.create();
// Use below code for HBase version 1.x.x or above.
Connection connection = ConnectionFactory.createConnection(conf);
TableName tableName = TableName.valueOf("users");
Table table = connection.getTable(tableName);
 
//Use below code HBase version 0.98.xx or below.
//HConnection connection = HConnectionManager.createConnection(conf);
//HTableInterface table = connection.getTable("users");
 
Get get = new Get(Bytes.toBytes("admin"));
Result result = table.get(get);
for (Cell c : result.rawCells()) {
    System.out.println(Bytes.toString(CellUtil.cloneRow(c))
        + "==> " + Bytes.toString(CellUtil.cloneFamily(c))
        + "{" + Bytes.toString(CellUtil.cloneQualifier(c))
        + ":" + Bytes.toLong(CellUtil.cloneValue(c)) + "}");
}
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
for (Result res : scanner) {
    for (Cell c : res.rawCells()) {
        System.out.println(Bytes.toString(CellUtil.cloneRow(c))
        + " ==> " + Bytes.toString(CellUtil.cloneFamily(c))
        + " {" + Bytes.toString(CellUtil.cloneQualifier(c))
        + ":" + Bytes.toLong(CellUtil.cloneValue(c))
        + "}");
    }
}

 

監控協處理器的時間

HBase 0.98.5引入了監控與執行一個給定的Coprocessor的時間有關的一些統計數據的能力。您可以通過HBase度量框架來查看這些統計信息(參見HBase指標或給定區域服務器的Web UI,通過Coprocessor指標選項卡。這些統計數據對於在集羣中對給定的Coprocessor的性能影響進行調試和基準測試是很有價值的。跟蹤的統計數據包括最小值、最大值、平均值和第90、95和第99個百分位數。所有的時間都以毫秒爲間隔。統計數據是通過Coprocessor執行樣本記錄來計算的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章