HBase 协处理器编程详解

本文详细地介绍了在 HBase0.98.11 版本下编写协处理器的细节,包括环境搭建和代码讲解。所有方法也适用于 HBase 1.0 版本。HBase 社区对于协处理器的文档一直比较缺乏,现有文档仅适用于 0.92 版本,早已过时。希望本文对需要使用新版本 HBase 协处理器的读者有所帮助。


Hbase 协处理器 Coprocessor 简介

HBase 是一款基于 Hadoop 的 key-value 数据库,它提供了对 HDFS 上数据的高效随机读写服务,完美地填补了 Hadoop MapReduce 仅适于批处理的缺陷,正在被越来越多的用户使用。作为 HBase 的一项重要特性,Coprocessor 在 HBase 0.92 版本中被加入,并广受欢迎。本文假设读者对 HBase 以及 Coprocessor 已经比较熟悉,因此并不打算进详细介绍 HBase Coprocessor 的基本概念。不熟悉 Hbase 协处理器原理的读者可以先阅读 HBase 博客上的文章 coprocessor_introduction进行一个基本的了解。

利用协处理器,用户可以编写运行在 HBase Server 端的代码。HBase 支持两种类型的协处理器,Endpoint 和 Observer。Endpoint 协处理器类似传统数据库中的存储过程,客户端可以调用这些 Endpoint 协处理器执行一段 Server 端代码,并将 Server 端代码的结果返回给客户端进一步处理,最常见的用法就是进行聚集操作。如果没有协处理器,当用户需要找出一张表中的最大数据,即 max 聚合操作,就必须进行全表扫描,在客户端代码内遍历扫描结果,并执行求最大值的操作。这样的方法无法利用底层集群的并发能力,而将所有计算都集中到 Client 端统一执行,势必效率低下。利用 Coprocessor,用户可以将求最大值的代码部署到 HBase Server 端,HBase 将利用底层 cluster 的多个节点并发执行求最大值的操作。即在每个 Region 范围内执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出,仅仅将该 max 值返回给客户端。在客户端进一步将多个 Region 的最大值进一步处理而找到其中的最大值。这样整体的执行效率就会提高很多。

另外一种协处理器叫做 Observer Coprocessor,这种协处理器类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。Observer Coprocessor 就是一些散布在 HBase Server 端代码中的 hook 钩子,在固定的事件发生时被调用。比如:put 操作之前有钩子函数 prePut,该函数在 put 操作执行前会被 Region Server 调用;在 put 操作之后则有 postPut 钩子函数。

HBase 协处理器用途广泛,然而 HBase 的各种文档却没有关于其编程方法的详细介绍,给入门的新手带来很大的障碍。在前文提及的经典文章 coprocessor_introduction中有关于如何编写 Coprocessor 的一些描述,可惜该文章发表于 HBase 0.92 的流行时期,其编程方法在新版本的 HBase 中已经无法使用。如今广泛使用的是 0.98 版本,甚至更新的 1.0 版本,读者如果按照上文中的方法进行尝试,一定会摸不着头脑。
本文将实现两个具体的 Coprocessor,来分别讲述如何编写 0.98 版本 HBase 的协处理器,基本方法对于 HBase 1.0 版本也适用。




编写 Coprocessor 流程概述和开发环境准备
本文作者采用的操作系统为 CentOS 6.5,采用其他的 Linux 发行版也可以进行 HBase 开发,不过准备工作的细节稍有不同。不过总的说来需要以下这三个主要的工具:
  • JDK 1.6 以上版本
  • Hbase 0.98
  • Google Protobuf 2.5.0
HBase 0.98 可以使用 Java 6 或 Java 7,而到了 1.0 版本就必须使用 Java 7。但是 Java 8 则有一些问题。因此推荐大家直接使用 Java 7 的 JDK 进行开发。


安装 JDK1.7
在 Oracle 官网下载 JDK。
图 1. 下载 JDK

HBase 协处理器编程详解_第1张图片 


下载完成后解压,并移动到$HOME/tools 目录下。$HOME/tools 是我自己创建的一个目录,用来存放所有开发本文实例所需要的工具。
[Bash shell]  纯文本查看  复制代码
?
1
2
tar -xzvf jdk-7u67-linux-x64. tar .gz
mv –r jdk1.7.0_67 $HOME /tools


如果有 root 或者 sudo 的权限,您也可以直接下载 rpm 包,然后用 rpm 命令安装。本文做最一般性的假设,因此假设您没有 root 的权限。
将下载文件解压后,还需要修改环境变量。将下面的 export 语句加入.bashrc 或者.profile 中均可。这样下次登录时这些环境变量将自动生效。

[Bash shell]  纯文本查看  复制代码
?
1
2
export JAVA_HOME=$HOME /tools/jdk1 .7.0_67/
export PATH=$JAVA_HOME /bin :$PATH


安装 Google Protobuf
老版本的 HBase(即 HBase 0.96 之前) 采用 Hadoop RPC 进行进程间通信。在 HBase 0.96 版本中,引入了新的进程间通信机制 protobuf RPC,基于 Google 公司的 protocol buffer 开源软件。HBase 需要使用 Protobuf 2.5.0 版本。这里简单介绍其安装过程:
首先需要下载 Protobuf 2.5.0 版本的源代码安装包,如果无法访问,可以在  csdn 找到下载。
[Bash shell]  纯文本查看  复制代码
?
1
2
wget href="https: //protobuf .googlecode.com /files/protobuf-2 .5.0. tar .bz2
  tar xjvf protobuf-2.5.0. tar .bz2

确保您已经安装了 gcc 和 gcc-c++包,然后进行编译安装:

[Bash shell]  纯文本查看  复制代码
?
1
2
3
mkdir $HOME /tools/protobuf-2 .5.0
. /configure --prefix=$HOME /tools/protobuf-2 .5.0
make ; make install


编译成功后编辑环境变量,加入 protoc 的路径
[Bash shell]  纯文本查看  复制代码
?
1
2
export PROTO_HOME=$HOME /tools/protobuf-2 .5.0
export PATH=$PROTO_HOME:$PATH



安装 Maven
本文采用 Maven 进行 Java 工程创建和编译,因此需要安装 Maven。您也可以采用其他您所喜欢的 Java 开发工具。
在 Maven 的官方网站可以下载 Maven 的二进制包,选择版本 3 以上的均可,本文采用最新的 Maven3.3.1。
图 2. 下载 Maven
HBase 协处理器编程详解_第2张图片 

下载完毕后解压。将解压后的二进制文件夹移动到$HOME/tools 下,以便于将来清理环境。最后修改环境变量即可。

[Bash shell]  纯文本查看  复制代码
?
1
2
3
4
5
tar -xzvf apache-maven-3.3.1-bin. tar .gz
mv apache-maven-3.3.1 $HOME /tools
vi .bashrc
  export MAVEN_HOME=$HOME /tools/apache-maven-3 .3.1
  export PATH=$MAVEN_HOME /bin :$PATH


安装 HBase
如果您已经安装了 HBase,并且其版本高于 0.96,那么请略过本节。写作本文时,HBase0.98 的最新版本为 0.98.11,因此这里简单介绍 HBase 0.98.11 版本的安装,本文的示例程序也将在这个版本的 HBase 中部署运行。
本文介绍的协处理器编写方法可以在任何高于 0.96 版本的 HBase 上运行,包括 HBase 1.0。0.94 版本的协处理器开发方法有所不同,在前文提及的经典文章  coprocessor_introduction中有详细介绍,互联网上也有大量的文章讲述老版本的协处理器编写方法。本文不再赘述,而着重讲述变化后的 HBase 协处理器开发方法。
在 HBase 网站下载:

图 3. 下载 HBase

HBase 协处理器编程详解_第3张图片 

下载完毕照例解压,修改环境变量。

[Bash shell]  纯文本查看  复制代码
?
1
2
3
4
5
tar xzvf hbase-0.98.11-hadoop2-bin. tar .gz
mv hbase-0.98.11-hadoop2
vi ~/.bashrc
  export HBASE_HOME=$HOME /tools/hbase-0 .98.11-hadoop2
  export PATH=$HBASE_HOME /bin :$PATH


为了运行本文中的实例,我们仅需要 HBase 运行在 standalone 模式即可。为此,无需修改任何配置,直接启动 HBase 即可。
[Bash shell]  纯文本查看  复制代码
?
1
start-hbase.sh


用 jps 命令查看,应该有一个 HMaster 的进程在运行。如果看到该进程,那么恭喜,您已经建立了一个完整的开发环境,足以满足本文的需要了。
进入细节之前,我们先从整体上了解一下开发 HBase 协处理器的流程。对于 Endpoint 类型的协处理器,其开发流程如下:

第一步是建立一个 Java 工程;第二步是定义用户 ClientHBase 通信的 RFC,采用 Protobuf 语言和工具完成定义;第三步是编写 HBase 协处理器的 Client 端和 Server 端代码,其中,Client 端代码负责调用协处理器并处理返回结果,Server 端代码将运行在 Region Server 上,实现具体的任务;最后需要对编译好的代码进行部署和测试。

对于 Observer 类型的协处理器,不需要定义 RPC,也不需要开发客户端代码。当相应的事件发生时,Observer 代码将自动在 Server 端执行。因此仅仅需要编写 Server 端的代码即可。

一个应用实例
本文将通过一个具体实例,来演示两种协处理器的开发方法的详细实现过程。

在管理 HBase 应用的过程中,笔者常想知道某个 Region 中数据行的个数总和,即 row count,或者整个 table 的数据量。本文将用“行数”来指称 row count。可以用 HBase Shell 的 count 命令来获取某张表的数据量。不过这是一个全表扫描过程,非常浪费资源,也很慢;另外一方面,还没有一个快捷的方法来获得单个 Region 的行数。

为此,我打算利用 Coprocessor 来实现一个简单的工具来帮助我实现以上的需求。其工作原理如下:
利用 Observer 协处理器在每一次 put 操作时,将统计该 Region 的行数,并保存在一个计数器中;在每一次 delete 操作时,将该计数器减 1。利用 Endpoint 协处理器,将该计数器的数值返回给 Client 端调用;为了在 Observer 和 Endpoint 协处理间共享行数计数器,我们将该计数器保存在 ZooKeeper 中。在客户端,调用 Endpoint 协处理器获取指定 Region 的行数计数器,并将所有的返回值求和即可。基本过程如下图所示:
图 4. 整体流程
HBase 协处理器编程详解_第4张图片 



创建 maven 工程
创建一个工程代码目录,用 {PROJECT_HOME} 来代表您创建的目录,后续开发的所有代码都将放在这里:
[Bash shell]  纯文本查看  复制代码
?
1
$ mkdir $PROJECT_HOME


用如下 Maven 命令创建工程:
[Bash shell]  纯文本查看  复制代码
?
1
2
3
$ cd $PROJECT_HOME
$ mvn archetype:generate -DgroupId=org.ibm.developerworks -DartifactId=regionCount
                                 -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode= false


Endpoint 协处理器

在本文中,Endpiont 协处理器的工作十分简单。仅仅返回 Region 的行数计数器即可,可以归纳为:读取一个值,然后返回它。但是即便是如此简单的一个操作,为了实现它,必须首先编写协处理器的框架。本文试图为大家提供一个尽量完整的参考。对于有经验的 Java 开发人员,以下的描述恐怕略显啰嗦,还请见谅。

用 Protobuf 编写和定义 RPC

如前所述 Endpoint 协处理器读取 Region 的行数计数器,然后将该值返回给调用的客户端。因此 RPC 需要一个整数类型的返回值代表行数。仅仅返回行数的情况下,客户端并不需要为 RPC 定义任何输入参数,不过为了演示输入和输出,我们额外为这个 RPC 设计了一个输入参数:reCount。这个参数是一个布尔变量,当为 true 时,表明用户需要 Endpoint 扫描遍历 Region 来计算行数;当其为 false,表示直接使用 Observer 协处理器维护的计数器。前者需要扫描整个 Region,非常慢;后者效率很高。
通过这种方法,我们也可以检验数据的正确性,因为遍历 Region 得到的行数是最准确的。最终的 RPC 定义如下。


清单 1. getRowCount RPC proto 定义

[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
option java_package = "org.ibm.developerworks" ;
  
option java_outer_classname = "getRowCount" ;
option java_generic_services = true ;
option optimize_for = SPEED;
  
message getRowCountRequest{
  required bool reCount = 1;
}
  
message getRowCountResponse {
  optional int64 rowCount = 1;
}
  
  
service ibmDeveloperWorksService {
  rpc getRowCount(getRowCountRequest)
  returns(getRowCountResponse);
}


将以上代码保存为文件 ibmDeveloperworksDemo.proto。可以看到,这里定义了一个 RPC,名字叫做 getRowCount。该 RPC 有一个入口参数,用消息 getRowCountRequest 表示;RPC 的返回值用消息 getRowCountResponse 表示。Service 是一个抽象概念,RPC 的 Server 端可以看作一个 Service,提供某种服务。在 HBase 协处理器中,Service 就是 Server 端需要提供的 Endpoint 协处理器服务,可以为 HBase 的客户端提供服务。在一个 Service 中可以提供多个 RPC,在本文中,我们仅仅定义了一个 RPC,实际工作中往往需要定义多个。
将该文件存放在工程的 src/main/protobuf 目录下。

[Bash shell]  纯文本查看  复制代码
?
1
2
$ mkdir $PROJECT_HOME /rowCount/src/main/protobuf
$ mv ibmDeveloperworksDemo.proto $PROJECT_HOME /rowCount/src/main/protobuf

用 Protobuf 编译器将该 proto 定义文件编译为 Java 代码,并放到 Maven 工程下。
[Bash shell]  纯文本查看  复制代码
?
1
2
$ cd $PROJECT_HOME /rowCount/src/main/protobuf
$ protoc --java_out=$PROJECT_HOME /rowCount/src/main/java ibmDeveloperworksDemo.proto


现在可以看到在工程的 src/main/java/org/ibm/developerworks 目录下生成了一个名为 getRowCount.java 的文件。这个 Java 文件就是 RPC 的 Java 代码,在后续的 Server 端代码和 Client 端代码中都要用到这个 Java 文件。
为了编译新生成的 Protobuf Java 代码,我们还需要修改 Maven 的 pom.xml 文件,加入对 protobuf-2.5.0 的依赖,这样 Maven 就可以自动下载相应的 jar 包,完成编译。
在 pom.xml 文件中加入如下的内容即可:
清单 2. Protobuf 在 pom.xml 中的依赖

[Bash shell]  纯文本查看  复制代码
?
1
2
3
4
5
<dependency>
  <groupId>com.google.protobuf< /groupId >
  <artifactId>protobuf-java< /artifactId >
  <version>2.5.0< /version >
< /dependency >

现在可以尝试进行第一编译了:
[Bash shell]  纯文本查看  复制代码
?
1
mvn clean compile



如果出现错误,您需要仔细查看代码是否在编辑的时候出错。本文的附件中有所有的示例代码,仅供参考。

实现 Server 端代码
在工程目录的 src/main/java/org/ibm/developerworks/下建立一个 coprocessor 的目录,存放我们即将开发的 Server 端 Endpoint 协处理器代码。

[Bash shell]  纯文本查看  复制代码
?
1
$ mkdir $PROJECT_HOME /rowCount/src/main/java/org/ibm/developerworks/coprocessor



Server 端的代码就是一个 Protobuf RPC 的 Service 实现,即通过 Protobuf 提供的某种服务。其开发内容主要包括:
  • 实现 Coprocessor 的基本框架代码
  • 实现服务的 RPC 具体代码




Endpoint 协处理的基本框架
Endpoint 是一个 Server 端 Service 的具体实现。它的实现有一些框架代码,这些框架代码与具体的业务需求逻辑无关。仅仅是为了和 HBase 的运行时环境协同工作而必须遵循和完成的一些粘合代码。因此多数情况下仅仅需要从一个例子程序拷贝过来并进行命名修改即可。不过我们还是完整地对这些粘合代码进行粗略的讲解以便更好地理解代码。
首先 Endpoint 协处理器是一个 Protobuf Service 的实现,因此需要它必须继承某个 Protobuf Service。我们在前面已经通过 proto 文件定义了 Service,命名为“ ibmDeveloperWorksService”,因此 Server 端代码需要重载该类,下图中相关代码用红色方框着重显示:
图 5. Endpoint 协处理器框架代码--父类    
其次,作为一个 HBase 的协处理器,Endpoint 还必须实现 HBase 定义的协处理器协议,用 Java 的接口来定义。具体来说就是 CoprocessorService 和  Coprocessor,这些 HBase 接口负责将协处理器和 HBase 的 RegionServer 等实例联系起来,以便协同工作。下图中相关代码用红色方框着重显示:
图 6. Endpoint 协处理器框架代码--接口    
Coprocessor 接口定义了两个接口函数,start 和 stop。
协处理器在 Region 打开的时候被 RegionServer 自动加载,并会调用器 start 接口,完成初始化工作。一般的该接口函数中仅仅需要将协处理器的运行上下文环境变量  CoprocessorEnviorment保存到本地即可。


清单 3.start 接口

[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// 这两个类成员是后续代码用来操作 ZooKeeper 的,在 start() 中进行初始化
  private String zNodePath = "/hbase/ibmdeveloperworks/demo" ;
  private ZooKeeperWatcher zkw = null;
  
  @Override
  public void start(CoprocessorEnvironment env ) throws IOException {
  if ( env instanceof RegionCoprocessorEnvironment) {
  this.re = (RegionCoprocessorEnvironment) env ;
  RegionServerServices rss = re.getRegionServerServices();
  // 获取 ZooKeeper 对象,这个 ZooKeeper 就是本 HBase 实例所连接的 ZooKeeper
  zkw = rss.getZooKeeper();
  // 用 region name 作为 znode 的节点名后缀
  zNodePath=zNodePath+re.getRegion().getRegionNameAsString();
  } else {
  throw new CoprocessorException( "Must be loaded on a table region!" );
  }
  }


CoprocessorEnviorment 保存了协处理器的运行环境,每个协处理器都是在一个 RegionServer 进程内运行,并隶属于某个 Region。通过该变量,可以获取 Region 的实例等 HBase 的运行时环境对象。

当需要在协处理器内调用 HBase 的服务或者 API 时,就必须通过该变量获取相应的 HBase 内部对象实例完成相关的操作。我们将在后续的 RPC 实现代码中给出详细的例子。

在 start 函数中,我们还初始化了一个 ZooKeeperWatcher,在文章的后续部分中,我们将详细介绍这个对象的用途。
Coprocessor 接口还定义了 stop() 接口函数。该函数在 Region 被关闭时调用,用来进行协处理器的清理工作。在本文中,我们没有任何清理工作,因此该函数什么也不干。

清单 4. stop 接口

[Bash shell]  纯文本查看  复制代码
?
1
2
3
4
@Override
public void stop(CoprocessorEnvironment env ) throws IOException {
// nothing to do
}


我们的协处理器还需要实现 CoprocessorService 接口。该接口仅仅定义了一个接口函数 getService()。我们仅需要将本实例返回即可。HBase 的 RegionServer 在接受到客户端的调用请求时,将调用该接口获取实现了 RPC Service 的实例,因此本函数一般情况下就是返回自身实例即可。

清单 5. getService 接口

[Bash shell]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
/**
* Just returns a reference to this object, which implements the RowCounterService interface.
*/
@Override
public Service getService() {
return this;
}


完成了以上三个接口函数之后,Endpoint 的框架代码就完成了。每个 Endpoint 协处理器都必须实现这些框架代码,而且写法雷同。
Endpoint 协处理器真正的业务代码都在每一个 RPC 函数的具体实现中。
在本文中,我们的 Endpoint 协处理器仅提供一个 RPC 函数,即 getRowCount。我将分别介绍编写该函数的几个主要工作:了解函数的定义,参数列表;处理入口参数;实现业务逻辑;设置返回参数。

函数定义
函数 getRowCount 在 Server 端的函数定义如下。
[Bash shell]  纯文本查看  复制代码
?
1
2
public void getRowCount(RpcController controller, getRowCount.getRowCountRequest request,
  RpcCallback<getRowCount.getRowCountResponse> done )


每一个 RPC 函数的参数列表都是固定的,有三个参数。第一个参数 RpcController 是固定的,所有 RPC 的第一个参数都是它,这是 HBase 的 Protobuf RPC 协议定义的;第二个参数为 RPC 的入口参数;第三个参数为返回参数。入口和返回参数分别由代码清单 1 的 proto 文件中的 getRowCountRequest 和 getRowCountResponse 定义。

分析入口参数
request 包含了入口参数,从 proto 定义中可以知道,这个入口参数只有一个 field,布尔类型的 reCount。我们将该参数从 Protobuf 消息中反序列化:

[Bash shell]  纯文本查看  复制代码
?
1
boolean reCount=request.getReCount();


如果您编写的 RPC 包含多个 field,每一个 field 都可以通过 request.getXXX() 函数来获得,其中 XXX 表示 field 的名字。

实现函数逻辑
我们的 RPC 的主要业务逻辑为获得 Region 的行数,当 reCount 为 true 时,需要遍历 Region 然后对结果集进行计数来获得行数;当 reCount 为 false 时,直接读取表示行数的变量。

我们先来看遍历 Region 的方法,这是最经典的行数统计实现,在 HBase 代码的 example 目录下也有现成的例子。通过 Scan 操作遍历 Region 中的每一行数据,在循环内将计数器累加即可。下面的代码清单中将错误处理部分去掉,以便很好地显示主要逻辑。

清单 6. 采用 scan 方法获取行数

[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
long getTableRowCountBatch(String tableName) {
  try{
// 连接 Hbase
  Configuration config = new Configuration();
  HConnection connection = HConnectionManager.createConnection(config);
  HTableInterface table = connection.getTable(tableName);
  
 
// 设置 request 参数
  org.ibm.developerworks.getRowCount.getRowCountRequest.Builder builder =
getRowCountRequest.newBuilder();
  builder.setReCount( false );
  
// 开始和结束 rowkey
byte[] s= Bytes.toBytes( "r1" );
byte[] e= Bytes.toBytes( "t1" );
// 调用 batchCoprocessorService
results = table.batchCoprocessorService(
  ibmDeveloperWorksService.getDescriptor().findMethodByName( "getRowCount" ),
  builder.build(),s, e,
  getRowCountResponse.getDefaultInstance());
  }
  Collection<getRowCountResponse> resultsc = results.values();
  for ( getRowCountResponse r : resultsc)
  {
  totalRowCount += r.getRowCount();
  }
  
  return totalRowCount;
  }


我们在前文定义了 Protobuf,其中 ibmDeveloperWOrksService 是我们定义的 Service,通过其 getDescriptor() 方法可以找到 Service 的描述符。RPC 方法在 Service 中定义,因此可以用 Service 描述符的 findMethodByName 方法找到具体的方法描述符,该描述符作为 batchCoprocessorService 的第一个参数,以便该方法可以知道调用哪个 RPC。关于 HBase 如何使用 Protobuf 实现 RPC 是一个大的话题,本文无法展开说明,感兴趣的读者可以进一步自行研究。

接下来需要给出 RPC 的入口参数和返回参数类型。和前面直接使用 coprocessorService 一样,还需要指定开始和结束的 rowkey,以便该方法找到正确的 Region 列表。直到 1.0 版本,HBase 在这里还有一些 bug,即 startkey 和 endkey 不能指定为 null。虽然 javadoc 中指明如果为 null,表示全表。但笔者在 0.98.11 版本中无法使用 null 参数,会执行出错。读者可以关注 HBASE-13417,看看什么时候可以正常使用 null 作为参数。
如上所述,一般情况下,使用 Endpoint 协处理器的频率不会太高。HBase 是一个存储数据的系统,最常用的应该是 get 和 put,如果频繁使用协处理器,也许说明您应该考虑其他的数据库系统。因此实践中,笔者尚未曾见过调用 batchCorpcoessorService 的例子。也可以理解为什么这个方法还存在如此低级的 bug。所以笔者不推荐读者使用该方法。

至此主要的客户端代码都已经实现,接下来我们需要编译并部署和执行。
我们将客户端代码也加在同一个 Maven 工程中,最终生成一个 jar 文件,包括了测试程序和协处理器代码。您也可以为客户端代码另外创建一个 Maven 工程。读者随意。

在本文中,最终的 jar 包为 rowCount.jar。


部署和运行

部署协处理器有三种方法。在  HBase 博客上的文章 coprocessor_introduction 中介绍了其中两种,即修改 hbase-site.xml 文件和通过 HBase Shell命令的方法。本文就不再重复。
第三种方法是通过 HBase API,用编程的方法为指定 table 部署协处理器。相应的 API 为 HTableDescriptor.addCoprocessor(String className)。本文将介绍这种方法。
代码清单 7 演示了如何创建测试用表的过程,通过调用 HTableDescriptor 的 addCoprocessor 方法为相应的 HBase 表部署了我们前文开发的两个协处理器。这段代码作为客户端代码的初始化部分,读者可以下载本文的附件获得完整代码。
清单 7. 利用 Java 代码实现协处理器部署
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
boolean createTable(string tableName) {
//HBase 1.0 创建 Table
Configuration config = new Configuration();
Table table = null;
TableName TABLE = TableName.valueOf(tableName);
Admin admin= new Admin(config);
HTableDescriptor tableDesc = new HTableDescriptor(TABLE);
 
//HBase 0.98 创建 Table
Configuration config = new Configuration();
HBaseAdmin admin = new HBaseAdmin(config);
  HTableDescriptor tableDesc = new HTableDescriptor(tableName);
  // 添加 coprocessor
tableDesc.addCoprocessor(“org.ibm.developerworks.coprocessor.getRowCountEndpoint”);
tableDesc.addCoprocessor(“org.ibm.developerworks.coprocessor.rowCountObserver”);
 
// 省去其他的 HTableDescriptor 操作代码
...
// 创建表
admin.createTable(tableDesc);
 
}


采用 Java 编程的方法部署协处理器,不需要对已经运行的 HBase 实例做任何修改,但是为了 HBase 能够加载协处理器的 jar 包,我们必须将其拷贝到 HBase 实例的 CLASSPATH 所指定的目录下。由于本文使用的是 HBase 的 standalone 模式,最简单的方法是将 jar 包放到$HBASE_HOME/lib 目录下。在真实的分布式环境下,需要将 jar 包拷贝到每台集群节点的$HBASE_HOME/lib 中,也有些公司将 jar 包上传到 HDFS 的指定目录,这样每台 RegionServer 都可以通过 HDFS 读取。具体的协处理器部署方法在 HBase0.96 之后和之前的版本都没有区别,读者可以参考前文提及的经典协处理器文章进一步了解。

本文的测试程序用法
本文附件中提供了一个完整的测试程序,用来演示本文中所有的代码功能。该测试程序创建用户输入的表,并自动采用 API 方法部署协处理器。然后,测试程序将首先向测试表插入 1000 条数据。然后根据用户输入,执行 N 条 delete 命令,保证最终表内的行数为用户指定的数字。
准备好数据之后,测试代码将分别使用本文介绍的三种客户端方法调用协处理器,并分析和输出结果。
在命令行直接运行例子程序:

[Bash shell]  纯文本查看  复制代码
?
1
java hbaseCoprocessorDemo test 1 666 rowkey


测试程序执行,创建 HBase 表”test”,第二个参数 1 的意思是采用 slow 方法获取 rowcount;如果第二个参数为 0,则例子代码会采用快速方法获取 rowcount;第三个参数即最终期望的 rowcount 个数;第四个参数表示是否进行全表扫描,如果用户将该参数省略,则进行全表 rowcount 统计,否则该参数表示 rowkey,程序就会去统计该 rowkey 所在 Region 上的 rowcount。

关于例子程序的运行细节本文就不再详述,读者可以下载附件查看更多细节,例子程序非常粗糙,仅为了演示基本的 Coprocessor 编程技术。读者可以自行优化和修改。

为了读者实验方便,附件代码包还提供了两个脚本:deploy.sh 进行代码编译和部署;test.sh 进行程序调用。读者可以直接使用这两个脚本进行测试。



调试
至此本系列告一段落,通过详细介绍开发协处理器的各种细节,希望能够对大家有所帮助。不过每个程序员都知道,多数情况下,编写代码本身并不会花费太多精力,最耗费时间的就是调试。然而对协处理器代码的调试是比较困难的。笔者也没有好的经验推荐给大家,唯一的调试手段就是通过日志打印,然后分析日志信息。

HBase 本身采用 log4j 进行日志打印,因此可以在 Coprocessor 中使用同样的日志方法,将信息打印到 HBase 的 Region Server 日志文件中,然后通过查看日志文件进而了解协处理器的运行情况。

使用 log4j 的方法非常简单,在类定义中加入 LOG 对象,并初始化。

[Bash shell]  纯文本查看  复制代码
?
1
private static final Log LOG = LogFactory.getLog(RowCountObserver.class);


此后,就可以在需要的地方进行打印了,即调用 LOG.info(“”) 即可。非常简单,读者可以参考附件中的代码,在此就不再赘述了。


结束语

HBase 的协处理器用途广泛,但是 HBase 的文档中对协处理器编程的细节却缺乏实用性的编程方法描述,希望本文能够为广大 HBase 用户提供一个较为详细的入门介绍。由于作者水平有限,文中可能有错误和不妥的地方,还希望读者不吝赐教。


《HBase 协处理器编程详解》系列第一部分介绍了 HBase 协处理器 Server 端代码的开发细节。分别实现了 Endpoint 和 Observer 协处理器,它们两个互相配合,为客户端提供 RPC 服务,获取指定 Region 上的总的 rowcount,即记录的个数。现在,本篇作为该系列第二部分,着重演示客户端应用程序中如何调用 Endpoint 协处理器,以便获得该服务。


实现 Client 端代码

HBase 提供了客户端 Java 包 org.apache.hadoop.hbase.client.coprocessor。它提供以下三种方法来调用协处理器提供的服务:
  • Table.coprocessorService(byte[])
  • Table.coprocessorService(Class, byte[], byte[],Batch.Call),
  • Table.coprocessorService(Class, byte[], byte[], Batch.Call, Batch.Callback)
Endpoing 协处理器在 Region 上下文中运行,一个 HBase 表可能有多个 Region。因此客户端可以指定调用某一个单个 Region 上的协处理器,在单个 Region 上进行处理并返回一定结果;也可以调用一定范围内的若干 Region 上的协处理器并发执行,并对结果进行汇总处理。针对不同的需要,可以选择以上三种方法,让我们来一一举例说明。

调用单个 Region 上的协处理器 RPC
第一个方法使用 API coprocessorService(byte[]),这个函数只调用单个 Region 上的协处理器。
该方法采用 RowKey 指定 Region。这是因为 HBase 的客户端很少会直接操作 Region,一般不需要知道 Region 的名字;况且在 HBase 中,Region 名会随时改变,所以用 rowkey 来指定 Region 是最合理的方式。使用 rowkey 可以指定唯一的一个 Region,如果给定的 rowkey 并不存在,只要在某个 Region 的 rowkey 范围内,依然可以用来指定该 Region。比如 Region 1 处理 [row1, row100] 这个区间内的数据,则 rowkey=row1 就由 Region 1 来负责处理,换句话说,我们可以用 row1 来指定 Region 1,无论 rowkey 等于”row1”的记录是否存在。
图 1. 调用单个 Region 上的协处理器
HBase 协处理器编程详解_第5张图片 


coprocessorService 方法返回类型为 CoprocessorRpcChannel 的对象,该 RPC 通道连接到由 rowkey 指定的 Region 上,通过这个通道,就可以调用该 Region 上部署的协处理器 RPC。
在本系列的第一部分,我们已经通过 Protobuf 定义了 RPC Service。调用 Service 的 newBlockingStub() 方法,将 CoprocessorRpcChannel 作为输入参数,就可以得到 RPC 调用的 stub 对象,进而调用远端的 RPC。这个过程是标准的 HBase RPC 调用,读者可以参考讲解 HBase RPC 的相关文章进一步学习。
清单 1. 获取单个 Region 的 rowcount
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
long singleRegionCount(String tableName, String rowkey,boolean reCount)
{
  long rowcount = 0;
  try{
  Configuration config = new Configuration();
  HConnection conn = HConnectionManager.createConnection(config);
  HTableInterface tbl = conn.getTable(tableName);
  // 获取 Channel
  CoprocessorRpcChannel channel = tbl.coprocessorService(rowkey.getBytes());
org.ibm.developerworks.getRowCount.ibmDeveloperWorksService.BlockingInterface service =
org.ibm.developerworks.getRowCount.ibmDeveloperWorksService.newBlockingStub(channel);
 
  // 设置 RPC 入口参数
org.ibm.developerworks.getRowCount.getRowCountRequest.Builder request =
org.ibm.developerworks.getRowCount.getRowCountRequest.newBuilder();
  request.setReCount(reCount);
 
  // 调用 RPC
  org.ibm.developerworks.getRowCount.getRowCountResponse ret =
  service.getRowCount(null, request.build());
 
  // 解析结果
  rowcount = ret.getRowCount();
  }
  catch(Exception e) {e.printStackTrace();}
  return rowcount;
  }


调用多个 Region 上的协处理器 RPC,不使用 callback
有时候客户端需要调用多个 Region 上的同一个协处理器,比如需要统计整个 table 的 rowcount,在这种情况下,需要所有的 Region 都参与进来,分别统计自己 Region 内部的 rowcount 并返回客户端,最终客户端将所有 Region 的返回结果汇总,就可以得到整张表的 rowcount。

这意味着该客户端同时和多个 Region 进行批处理交互。一个可行的方法是,收集每个 Region 的 startkey,然后循环调用第一种 coprocessorService 方法:用每一个 Region 的 startkey 作为入口参数,获得 RPC 通道,创建 stub 对象,进而逐一调用每个 Region 上的协处理器 RPC。这种做法需要写很多的代码,为此 HBase 提供了两种更加简单的 coprocessorService 方法来处理多个 Region 的协处理器调用。先来看第一种方法 coprocessorService(Class, byte[],byte[],Batch.Call),该方法有 4 个入口参数。第一个参数是实现 RPC 的 Service 类,即前文中的ibmDeveloperWorksService类。通过它,HBase 就可以找到相应的部署在 Region 上的协处理器,一个 Region 上可以部署多个协处理器,客户端必须通过指定 Service 类来区分究竟需要调用哪个协处理器提供的服务。

要调用哪些 Region 上的服务则由 startkey 和 endkey 来确定,通过 rowkey 范围即可确定多个 Region。为此,coprocessorService 方法的第二个和第三个参数分别是 startkey 和 endkey,凡是落在 [startkey,endkey] 区间内的 Region 都会参与本次调用。

第四个参数是接口类 Batch.Call。它定义了如何调用协处理器,用户通过重载该接口的 call() 方法来实现客户端的逻辑。在 call() 方法内,可以调用 RPC,并对返回值进行任意处理。即前文代码清单 1 中所做的事情。coprocessorService 将负责对每个 Region 调用这个 call 方法。

coprocessorService 方法的返回值是一个 map 类型的集合。该集合的 key 是 Region 名字,value 是 Batch.Call.call 方法的返回值。该集合可以看作是所有 Region 的协处理器 RPC 返回的结果集。客户端代码可以遍历该集合对所有的结果进行汇总处理。

这种 coprocessorService 方法的大体工作流程如下。首先它分析 startkey 和 endkey,找到该区间内的所有 Region,假设存放在 regionList 中。然后,遍历 regionList,为每一个 Region 调用 Batch.Call,在该接口内,用户定义了具体的 RPC 调用逻辑。最后 coprocessorService 将所有 Batch.Call.call() 的返回值加入结果集合并返回。如下图所示:

图 2. 调用多个 Region 上的协处理器--不使用 callback
HBase 协处理器编程详解_第6张图片 


下面,我们将利用这个方法对指定 table 进行 rowcount 统计。基本思路是对指定 table 的所有 Region 调用 getRowCount RPC 方法。最后将结果集进行累加求得最终结果。

因此为了指定调用 getRowCount RPC,第一个参数就是 ibmDeveloperWorksService.class。第二个参数和第三个参数都为 null,这样 coprocessorService 就会对指定 table 的所有 Region 进行处理。比较复杂的是第四个参数。

Batch.Call 是一个接口类,只提供了一个方法叫做 call (),coprocessorService 会为每一个 Region 调用 Batch.Call 接口定义的 call 方法,因此调用 RPC 的逻辑即在重载 call() 方法中实现。

call 方法的入口参数是 coprocessorService 的第一个参数的一个实例,通过它即可调用 RPC。本例中,我们在 call 方法内需要调用 Endpoint 协处理器提供的 getRowCount 方法,然后直接将该 RPC 的返回值返回即可。coprocessorService 会将 call 方法的返回值加入最终的结果集合中。

清单 2. Batch.Call 接口实现
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
Batch.Call<ibmDeveloperWorksService, getRowCountResponse> callable =
  new Batch.Call<ibmDeveloperWorksService, getRowCountResponse>() {
  ServerRpcController controller = new ServerRpcController();
  BlockingRpcCallback<getRowCountResponse> rpcCallback =
  new BlockingRpcCallback<getRowCountResponse>();
  // 下面重载 call 方法
  @Override
  public getRowCountResponse call(ibmDeveloperWorksService instance) throws IOException {
  // 初始化 RPC 的入口参数,设置 reCount 为 true
  //Server 端会进行慢速的遍历 region 的方法进行统计
  org.ibm.developerworks.getRowCount.getRowCountRequest.Builder builder =
                             getRowCountRequest.newBuilder();
  builder.setreCount( true );
  //RPC 调用
  instance.getRowCount(controller, builder.build(), rpcCallback);
  // 直接返回结果,即该 Region 的 rowCount
  return rpcCallback.get();
  }
  };


写好这个最复杂的 Batch.Call 接口类之后,在客户端代码里面只需要直接调用 coprocessorService 函数,然后对结果集合进行统计即可。下面的代码演示了基本完整的客户端代码。首先获得 table 的实例,然后调用 table 的 coprocessorService 方法,最后对结果进行汇总。HBase1.0 版本开始的 client 代码有所改变,但大家可以看到,这些改变实际上并不影响 Coprocessor 的调用方法,而仅仅是在 HBase 的连接初始化部分有所不同。在本文代码清单中笔者分别列举了 HBase1.0 的代码和 HBase0.98 的示例代码。而附件中的代码则都使用 0.98 版本的 API。

清单 3. 获取全表行数的代码
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
long getTableRowCountSlow(string tableName) {
  // 创建 Table 实例, HBase 1.0
  Connection connection = ConnectionFactory.createConnection(conf);
  Table table = connection.getTable(tableName);
  // 创建 HTable 实例,HBase 0.98
  HConnection connection = HConnectionManager.createConnection(config);
  HTable table = connection.getTable(tableName);
  
  Batch.Call<ibmDeveloperWorksService, getRowCountResponse> callable =
  ... 省略代码,参考代码清单 2
  results = table.coprocessorService(ibmDeveloperWorksService.class, null, null,
  callable);
  long totalRowCount = 0;
  for ( r : results)
  {
  totalRowCount += r.value();
  }
  return totalRowCount;
}



调用多个 Region 上的协处理器 RPC--使用 callback
coprocessorService 的第三种方法比第二个方法多了一个参数 callback。coprocessorService 第二个方法内部使用 HBase 自带的缺省 callback,该缺省 callback 将每个 Region 的返回结果都添加到一个 map 类型的结果集中,并将该集合作为 coprocessorService 方法的返回值。
这个结果集合的 key 是 Region 名字,value 是 call 方法的返回值。采用这种方法,客户端代码需要将 RPC 执行结果先保存在一个集合中,再进入一个循环,遍历结果集合进一步处理。
有些情况下这种使用集合的开销是不必要的。对每个 Region 的返回结果直接进行处理可以省去这些开销。具体过程如下图所示:
图 3. 调用多个 Region 上的协处理器--使用 callback HBase 协处理器编程详解_第7张图片 

HBase 提供第三种 coprocessorService 方法允许用户定义 callback 行为,coprocessorService 会为每一个 RPC 返回结果调用该 callback,用户可以在 callback 中执行需要的逻辑,比如执行 sum 累加。用第二种方法的情况下,每个 Region 协处理器 RPC 的返回结果先放入一个列表,所有的 Region 都返回后,用户代码再从该列表中取出每一个结果进行累加;用第三种方法,直接在 callback 中进行累加,省掉了创建结果集合和遍历该集合的开销,效率会更高一些。
因此我们只需要额外定义一个 callback 即可,callback 是一个 Batch.Callback 接口类,用户需要重载其 update 方法。
清单 4. Batch.Callback 接口实现
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
// 定义总的 rowCount 变量
final AtomicLong totalRowCount = new AtomicLong();
// 定义 callback
Batch.Callback< Long > callback =
  new Batch.Callback<Long>() {
  @Override
  public void update(byte[] region, byte[] row, getRowCountResponse result) {
  // 直接将 Batch.Call 的结果,即单个 region 的 rowCount 累加到 totalRowCount
  totalRowCount.getAndAdd(result.getRowCount());
  }
};


在使用第三种 coprocessorService 的例子代码中,我们将调用 Endpoint 协处理器 RPC getRowCount 的快速方法,即直接读取由 Endpoint 协处理和 Observer 协处理器共同维护的 rowCount 变量,而无需 scan 整个 region。因此这是一种快速方法。
为此我们在调用 RPC 的时候将 getRowCountRequest.reCount 设置为 false,其他代码和代码清单 3 一致。

清单 5. getTableRowCountFast 实现
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
long getTableRowCountFast(string tableName) {
  // 创建 Table 实例, HBase 1.0
  Connection connection = ConnectionFactory.createConnection(conf);
  TableName TABLE = TableName.valueOf(tableName);
  Table table = connection.getTable(TABLE);
  // 创建 HTable 实例,HBase 0.98
  HConnection connection = HConnectionManager.createConnection(config);
  HTable table = connection.getTable(tableName);
  
  Batch.Call<ibmDeveloperWorksService, getRowCountResponse> callable =
  new Batch.Call<ibmDeveloperWorksService, getRowCountResponse>() {
  ServerRpcController controller = new ServerRpcController();
  BlockingRpcCallback<getRowCountResponse> rpcCallback =
  new BlockingRpcCallback<getRowCountResponse>();
  // 下面重载 call 方法
  @Override
public getRowCountResponse call(ibmDeveloperWorksService instance)
  throws IOException {
  // 初始化 RPC 的入口参数,设置 reCount 为 false
  //Server 端会进行慢速的遍历 region 的方法进行统计
  org.ibm.developerworks.getRowCount.getRowCountRequest.Builder builder =
  getRowCountRequest.newBuilder();
  builder.setreCount( false );
  //RPC 调用
  instance.getRowCount(controller, builder.build(), rpcCallback);
  // 直接返回结果,即该 Region 的 rowCount
  return rpcCallback.get();
  }
  };
 
  // 定义总的 rowCount 变量
  AtomicLong totalRowCount = new AtomicLong();
  // 定义 callback
  Batch.Callback< Long > callback =
  new Batch.Callback<Long>() {
  @Override
  public void update(byte[] region, byte[] row, Long result) {
  // 直接将 Batch.Call 的结果,即单个 region 的 rowCount 累加到 totalRowCount
  totalRowCount.getAndAdd(result);
  }
  };
 
  table.coprocessorService( ibmDeveloperWorksService.class, null, null,
  callable, callback);
  return totalRowCount;
}


批处理 coprocessorService
除了以上三种直接调用 coprocessorService 的方法之外,HBase 还提供另外两个更加高效的客户端调用方法,能够对 coprocessorService 进行批处理,进一步提高调用效率:
<R extends com.google.protobuf.Message>
  • void batchCoprocessorService(MethodDescriptor methodDescriptor,
com.google.protobuf.Message request,
byte[] startKey,
byte[] endKey,
R responsePrototype,
Batch.Callback<R> callback)
  • Map<byte[],R> batchCoprocessorService(MethodDescriptor methodDescriptor,
com.google.protobuf.Message request,
byte[] startKey,
byte[] endKey,
R responsePrototype)
在之前的例子中,每次调用一个 RPC,客户端都将和 Region Server 进行一次网络交互。比如一个 table 有 10 个 Region,均匀分布在 2 个 Region Server 上,即每个 Region Server 上有 5 个 Region。采用前面的方法,需要 10 次 RPC 过程。采用 BatchCoprocessorService,则 HBase 会自动将属于同一个 Region Server 的多个 RPC 打包在一个 RPC 调用过程中,对于前面的例子,2 个 Region Server 上分布 10 个 Region,采用这两个方法就可以只发起 2 次网络交互,一次将 5 个 RPC 调用发给 Region Server A;另一次将剩下 5 个 RPC 调用批处理发给 Region Server B。这样就节约了网络交互的开销,更加高效。

图 4. 调用 batchCoprocessorService
HBase 协处理器编程详解_第8张图片 

由于一般情况下,调用协处理器的频率并不会很高,对于调用效率的苛求比较少见。所以很少见到使用这两种 API 的例子,不推荐使用。同时还存在一些问题,本文稍后会有所描述。
代码清单 6 给出使用 batchCoprocessorService 的例子代码,笔者测试可以正确运行,读者可以在本文附件中获得完整代码。
清单 6. batchCoprocessorService 的调用方法
[Bash shell]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
long getTableRowCountBatch(String tableName) {
  try{
// 连接 Hbase
  Configuration config = new Configuration();
  HConnection connection = HConnectionManager.createConnection(config);
  HTableInterface table = connection.getTable(tableName);
  
 
// 设置 request 参数
  org.ibm.developerworks.getRowCount.getRowCountRequest.Builder builder =
getRowCountRequest.newBuilder();
  builder.setReCount( false );
  
// 开始和结束 rowkey
byte[] s= Bytes.toBytes( "r1" );
byte[] e= Bytes.toBytes( "t1" );
// 调用 batchCoprocessorService
results = table.batchCoprocessorService(
  ibmDeveloperWorksService.getDescriptor().findMethodByName( "getRowCount" ),
  builder.build(),s, e,
  getRowCountResponse.getDefaultInstance());
  }
  Collection<getRowCountResponse> resultsc = results.values();
  for ( getRowCountResponse r : resultsc)
  {
  totalRowCount += r.getRowCount();
  }
  
  return totalRowCount;
  }


我们在前文定义了 Protobuf,其中 ibmDeveloperWOrksService 是我们定义的 Service,通过其 getDescriptor() 方法可以找到 Service 的描述符。RPC 方法在 Service 中定义,因此可以用 Service 描述符的 findMethodByName 方法找到具体的方法描述符,该描述符作为 batchCoprocessorService 的第一个参数,以便该方法可以知道调用哪个 RPC。关于 HBase 如何使用 Protobuf 实现 RPC 是一个大的话题,本文无法展开说明,感兴趣的读者可以进一步自行研究。

接下来需要给出 RPC 的入口参数和返回参数类型。和前面直接使用 coprocessorService 一样,还需要指定开始和结束的 rowkey,以便该方法找到正确的 Region 列表。直到 1.0 版本,HBase 在这里还有一些 bug,即 startkey 和 endkey 不能指定为 null。虽然 javadoc 中指明如果为 null,表示全表。但笔者在 0.98.11 版本中无法使用 null 参数,会执行出错。读者可以关注 HBASE-13417,看看什么时候可以正常使用 null 作为参数。

如上所述,一般情况下,使用 Endpoint 协处理器的频率不会太高。HBase 是一个存储数据的系统,最常用的应该是 get 和 put,如果频繁使用协处理器,也许说明您应该考虑其他的数据库系统。因此实践中,笔者尚未曾见过调用 batchCorpcoessorService 的例子。也可以理解为什么这个方法还存在如此低级的 bug。所以笔者不推荐读者使用该方法。

至此主要的客户端代码都已经实现,接下来我们需要编译并部署和执行。
我们将客户端代码也加在同一个 Maven 工程中,最终生成一个 jar 文件,包括了测试程序和协处理器代码。您也可以为客户端代码另外创建一个 Maven 工程。读者随意。

在本文中,最终的 jar 包为 rowCount.jar。


部署和运行
部署协处理器有三种方法。在  HBase 博客上的文章 coprocessor_introduction 中介绍了其中两种,即修改 hbase-site.xml 文件和通过HBase Shell命令的方法。本文就不再重复。
第三种方法是通过 HBase API,用编程的方法为指定 table 部署协处理器。相应的 API 为 HTableDescriptor.addCoprocessor(String className)。本文将介绍这种方法。
代码清单 7 演示了如何创建测试用表的过程,通过调用 HTableDescriptor 的 addCoprocessor 方法为相应的 HBase 表部署了我们前文开发的两个协处理器。这段代码作为客户端代码的初始化部分,读者可以下载本文的附件获得完整代码。
清单 7. 利用 Java 代码实现协处理器部署
[Bash shell]  纯文本查看  复制代码
?
01

你可能感兴趣的:(HBase 协处理器编程详解)