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执行样本记录来计算的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值