1.简介

  前篇文章[How to] 使用HBase协处理器---基本概念和regionObserver的简单实现中提到了两种不同的协处理器,并且实现了regionObserver。

本文将介绍如何使用EndPoint协处理器类型。

  与Observer类型不同的是,Endpoint协处理器需要与服务区直接通信,服务端是对于Protobuf Service的实现,所以两者直接会有一个机遇protocl的RPC接口,客户端和服务端都需要进行基于接口的代码逻辑实现。

2.Endpoint的服务端实现

  如前所述,Endpoint类比于数据库中的存储过程,他出发服务端的基于region的同步运行,再将各个结果在客户端搜集后归并计算。特点类似于传统的MR框架,在服务端MAP在客户端Reduce。相对于Observer来说开发难度大一点。

  

  需求确定:

  和Observer一样,在完成实现前先来了解需求,本次样例完成的功能是机遇前篇博客的表

1.可以统计表coprocessor_table下某字段字段的值总和
2.可以统计表coprocessor_table的记录行数  

  用 Protobuf 编写和定义 RPC:

  如前所述,客户端和服务端之间存在RPC通信,所以两者间需要确定接口,HBase的协处理器是通过Protobuf协议来实现数据交换的,所以需要通过Protobuf来定义接口。

option java_package = "cn.com.newbee.feng.Statistics.protointerface";

option java_outer_classname = "MyStatisticsInterface";
option java_generic_services = true;
option optimize_for = SPEED; message getStatisticsRequest{
required string type = ;
optional string famillyName = ;
optional string columnName = ;
} message getStatisticsResponse {
optional int64 result = ;
} service myStatisticsService {
rpc getStatisticsResult(getStatisticsRequest)
returns(getStatisticsResponse);
}

  上述文本中定义了一个protolbuf的服务接口myStatisticsService,这个接口提供一个方法叫做getStatisticsResult,方法的请求被包装在getStatisticsRequest中,其中type计算类型参数是必须的,而列族好列名是不必须的,因为根据需求到计算类型为计算记录行数的时候并不需要这两个参数。返回值被包装在getStatisticsResponse,根据需求返回整数值即可。

上述文件被保存在以.proto结尾的文本文件中。

  编译接口文件

  当接口文件确定和产生后需要使用protol编译器进行编译,我们的运行环境是jdk,所以将其编译成java代码。使用如下命令:

protoc --java_out=/Users/apple/Desktop/workspace/Test-HBase-Endpoint/src statistics.proto

  在编译时候遇到一个坑:在我的环境中必须要到proto文件所在的目录才能执行上述语句。

  编译完成后将会在option java_package下生成option java_outer_classname指定的类。此类不要进行任何的修改。后续都是围绕这个接口类进行代码的实现。

  

  服务端代码实现

  所谓基于框架的功能实现无非就是两点:

1. 按照要求搭建框架代码

   2. 基于框架代码填充自己的业务逻辑

  Endpoint的基本框架代码我剥离出来如下:需要领悟的是每一个Endpoint的协处理器是运行在单个region上的,所以每一个结算结果都是对于单个region的数据范围的计算结果。

public class Endpoint类名称 extends
protol类.服务接口名称 implements Coprocessor,
CoprocessorService {
  // 单个region的上下文环境信息
private RegionCoprocessorEnvironment envi; // rpc服务,返回本身即可,因为此类实例就是一个服务实现
@Override
public Service getService() {
return this;
} // 协处理器是运行于region中的,每一个region都会加载协处理器
// 这个方法会在regionserver打开region时候执行(还没有真正打开)
@Override
public void start(CoprocessorEnvironment env) throws IOException {// 需要检查当前环境是否在region上
if (env instanceof RegionCoprocessorEnvironment) {
this.envi = (RegionCoprocessorEnvironment) env; } else {
throw new CoprocessorException("Must be loaded on a table region!");
} } // 这个方法会在regionserver关闭region时候执行(还没有真正关闭)
@Override
public void stop(CoprocessorEnvironment env) throws IOException {// nothing to do } // 服务端(每一个region上)的接口实现方法
// 第一个参数是固定的,其余的request参数和response参数是proto接口文件中指明的。
@Override
public void 服务接口方法(RpcController controller,
服务接口参数 request,
RpcCallback<服务接口response> done) {// 单个region上的计算结果值,根据需要实现
int result = 0; // 定义返回response      Endpoint类名称.返回response.Builder responseBuilder = Endpoint类名称.返回response .newBuilder();     
     // 计算结果值逻辑代码
     // 返回结果值
responseBuilder.setResult(result);
done.run(responseBuilder.build());
return;
} }

  根据框架代码,根据需求的、来填充完善业务逻辑,就本次需求的具体实现代码如下:

/**
* 统计行数和统计指定列值加和的Endpoint实现
*
* @author newbee-feng
*
*/
public class MyStatisticsEndpoint extends
MyStatisticsInterface.myStatisticsService implements Coprocessor,
CoprocessorService {
private static final Log LOG = LogFactory.getLog(MyStatisticsEndpoint.class); private static final String STATISTICS_COUNT = "COUNT"; private static final String STATISTICS_SUM = "SUM"; // 单个region的上下文环境信息
private RegionCoprocessorEnvironment envi; // rpc服务,返回本身即可,因为此类实例就是一个服务实现
@Override
public Service getService() {
return this;
} // 协处理器是运行于region中的,每一个region都会加载协处理器
// 这个方法会在regionserver打开region时候执行(还没有真正打开)
@Override
public void start(CoprocessorEnvironment env) throws IOException {
LOG.info("MyStatisticsEndpoint start");
// 需要检查当前环境是否在region上
if (env instanceof RegionCoprocessorEnvironment) {
this.envi = (RegionCoprocessorEnvironment) env; } else {
throw new CoprocessorException("Must be loaded on a table region!");
} } // 这个方法会在regionserver关闭region时候执行(还没有真正关闭)
@Override
public void stop(CoprocessorEnvironment env) throws IOException {
LOG.info("MyStatisticsEndpoint stop");
// nothing to do } // 服务端(每一个region上)的接口实现方法
// 第一个参数是固定的,其余的request参数和response参数是proto接口文件中指明的。
@Override
public void getStatisticsResult(RpcController controller,
getStatisticsRequest request,
RpcCallback<getStatisticsResponse> done) {
LOG.info("MyStatisticsEndpoint##getStatisticsResult call");
// 单个region上的计算结果值
int result = 0; // 定义返回response
MyStatisticsInterface.getStatisticsResponse.Builder responseBuilder = MyStatisticsInterface.getStatisticsResponse
.newBuilder(); // type就是在proto中定义参数字段,如果有多个参数字段可以都可以使用request.getXxx()来获取
String type = request.getType(); // 查看当前需要做和计算
if (null == type) {
LOG.error("the type is null");
responseBuilder.setResult(result);
done.run(responseBuilder.build());
return;
} // 当进行行数统计的时
if (STATISTICS_COUNT.equals(type)) {
LOG.info("MyStatisticsEndpoint##getStatisticsResult call " + STATISTICS_COUNT);
InternalScanner scanner = null;
try {
Scan scan = new Scan();
scanner = this.envi.getRegion().getScanner(scan);
List<Cell> results = new ArrayList<Cell>();
boolean hasMore = false; do {
hasMore = scanner.next(results);
result++;
} while (hasMore);
} catch (IOException ioe) {
LOG.error("error happend when count in "
+ this.envi.getRegion().getRegionNameAsString()
+ " error is " + ioe);
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (IOException ignored) {
// nothing to do
}
}
} }
// 当进行指定列值统计的时候
else if (STATISTICS_SUM.equals(type)) {
LOG.info("MyStatisticsEndpoint##getStatisticsResult call " + STATISTICS_SUM);
// 此时需要去检查客户端是否指定了列族和列名
String famillyName = request.getFamillyName();
String columnName = request.getColumnName(); // 此条件下列族和列名是必须的
if (!StringUtils.isBlank(famillyName)
&& !StringUtils.isBlank(columnName)) { InternalScanner scanner = null;
try {
Scan scan = new Scan();
scan.addColumn(Bytes.toBytes(famillyName), Bytes.toBytes(columnName));
scanner = this.envi.getRegion().getScanner(scan);
List<Cell> results = new ArrayList<Cell>();
boolean hasMore = false;
do {
hasMore = scanner.next(results);
if (results.size() == 1)
{
// 按行读取数据,并进行加和操作
result = result + Integer.valueOf(Bytes.toString(CellUtil.cloneValue(results.get(0))));
} results.clear();
} while (hasMore); } catch (Exception e) {
LOG.error("error happend when count in "
+ this.envi.getRegion().getRegionNameAsString()
+ " error is " + e);
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (IOException ignored) {
// nothing to do
}
}
} } else {
LOG.error("did not specify the famillyName or columnName!");
} }
// 如果不在此范围内则不计算
else {
LOG.error("the type is not match!");
} LOG.info("MyStatisticsEndpoint##getStatisticsResult call end"); responseBuilder.setResult(result);
done.run(responseBuilder.build());
return; } }

   以上代码的逻辑实现请参考代码内注释。需要注意的是我们可以使用log4j进行日志的打印,这些日志会被服务端程序打印,便于定位问题。

  部署Endpoint协处理器

  Endpoint的部署和Observer部署一样,在这里我还是使用shell命令行的方式进行部署。

  首先需要将Endpoint实现打成jar,然后上传至HBase所在的环境的hdfs中,在hbase shell中执行如下命令:

hbase(main):033:0> alter 'coprocessor_table' , METHOD =>'table_att','coprocessor'=>'hdfs://ns1/testdata/Test-HBase-Endpoint.jar|cn.com.newbee.feng.MyStatisticsEndpoint|1002'
Updating all regions with the new schema...
0/1 regions updated.
1/1 regions updated.
Done.
0 row(s) in 2.2770 seconds

  检查是否加载成功:现在表上连同之前的Observer协处理器,一共有两个协处理器存在。

hbase(main):034:0> describe
describe describe_namespace
hbase(main):034:0> describe 'coprocessor_table'
Table coprocessor_table is ENABLED
coprocessor_table, {TABLE_ATTRIBUTES => {coprocessor$1 => 'hdfs://ns1/testdata/Te
st-HBase-Observer.jar|cn.com.newbee.feng.MyRegionObserver|1001', coprocessor$2 =>
'hdfs://ns1/testdata/Test-HBase-Endpoint.jar|cn.com.newbee.feng.MyStatisticsEndp
oint|1002'}
COLUMN FAMILIES DESCRIPTION
{NAME => 'F', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW', REPLICATION_SC
OPE => '0', VERSIONS => '1', COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL => '
FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536', IN_MEMORY => 'fals
e', BLOCKCACHE => 'true'}
1 row(s) in 0.0210 seconds

  

 3. 总结

  服Endpoint协处理器如前所述,是运行于region上的,换句话你说每一个加载了Endpoint协处理器的region即是一个RPC的服务端。Endpoint的业务接口中的数据只限于计算所在region的数据,

客户端可以向不同region获取服务,计算结果并在服务端将结果合并。当然向哪些region请求服务也是可以控制的,后续在介绍Endpoint客户端代码实现的时候介绍。

  

参考:

  http://www.ibm.com/developerworks/cn/opensource/os-cn-hbase-coprocessor1/index.html

代码下载:

  https://github.com/xufeng79x/Test-HBase-Endpoint

[How to] 使用HBase协处理器---Endpoint服务端的实现的更多相关文章

  1. [How to] 使用HBase协处理器---Endpoint客户端代码的实现

    1.简介 不同于Observer协处理器,EndPoint由于需要同region进行rpc服务的通信,以及客户端出数据的归并,需要自行实现客户端代码. 基于[How to] 使用HBase协处理器-- ...

  2. HBase 协处理器编程详解第一部分:Server 端代码编写

    Hbase 协处理器 Coprocessor 简介 HBase 是一款基于 Hadoop 的 key-value 数据库,它提供了对 HDFS 上数据的高效随机读写服务,完美地填补了 Hadoop M ...

  3. hbase协处理器编码实例

    Observer协处理器通常在一个特定的事件(诸如Get或Put)之前或之后发生,相当于RDBMS中的触发器.Endpoint协处理器则类似于RDBMS中的存储过程,因为它可以让你在RegionSer ...

  4. HBase 学习之路(八)——HBase协处理器

    一.简述 在使用HBase时,如果你的数据量达到了数十亿行或数百万列,此时能否在查询中返回大量数据将受制于网络的带宽,即便网络状况允许,但是客户端的计算处理也未必能够满足要求.在这种情况下,协处理器( ...

  5. HBase 系列(八)——HBase 协处理器

    一.简述 在使用 HBase 时,如果你的数据量达到了数十亿行或数百万列,此时能否在查询中返回大量数据将受制于网络的带宽,即便网络状况允许,但是客户端的计算处理也未必能够满足要求.在这种情况下,协处理 ...

  6. 入门大数据---Hbase协处理器详解

    一.简述 Hbase 作为列族数据库最经常被人诟病的特性包括:无法轻易建立"二级索引",难以执 行求和.计数.排序等操作.比如,在旧版本的(<0.92)Hbase 中,统计数 ...

  7. java实现服务端守护进程来监听客户端通过上传json文件写数据到hbase中

    1.项目介绍: 由于大数据部门涉及到其他部门将数据传到数据中心,大部分公司采用的方式是用json文件的方式传输,因此就需要编写服务端和客户端的小程序了.而我主要实现服务端的代码,也有相应的客户端的测试 ...

  8. [HBase] 服务端RPC机制及代码梳理

    基于版本:CDH5.4.2 上述版本较老,但是目前生产上是使用这个版本,所以以此为例. 1. 概要 说明: 客户端API发送的请求将会被RPCServer的Listener线程监听到. Listene ...

  9. 11 hbase源码系列(十一)Put、Delete在服务端是如何处理

    hbase源码系列(十一)Put.Delete在服务端是如何处理?    在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTab ...

随机推荐

  1. BZOJ4890 Tjoi2017城市

    显然删掉的边肯定是直径上的边.考虑枚举删哪一条.然后考虑怎么连.显然新边应该满足其两端点在各自树中作为根能使树深度最小.只要线性求出这个东西就可以了,这与求树的重心的过程类似. #include< ...

  2. 洛谷 P2888 [USACO07NOV]牛栏Cow Hurdles

    题目戳 题目描述 Farmer John wants the cows to prepare for the county jumping competition, so Bessie and the ...

  3. Remember the Word UVALive - 3942(dp+trie)

    题意: 给S个不同的单词和一个长字符串 问将其分解为若干个单词有多少种方法(单词可重复使用) 解析: dp[i]表示在这个字符串中以某个位置i为起点的 的一段子字符串 则这个子字符串若存在某个前缀恰好 ...

  4. Connections between cities HDU - 2874(最短路树 lca )

    题意: 给出n个点m条边的图,c次询问 求询问中两个点间的最短距离. 解析: Floyd会T,所以用到了最短路树..具体思想为: 设k为u和v的最近公共祖先 d[i] 为祖结点到i的最短距离  则di ...

  5. [cogs1065]绿豆蛙的归宿

    1065. [Nescafe19] 绿豆蛙的归宿 [题目描述] 给出一个有向无环的连通图,起点为1终点为N,每条边都有一个长度.绿豆蛙从起点出发,走向终点.到达每一个顶点时,如果有K条离开该点的道路, ...

  6. 【JavaScript】基本类型和引用类型的值、引用类型

    一.前言        接着上一篇继续记笔记 二.内容         动态的属性 var person = new Object(); person.name = "Nicholas&qu ...

  7. 【BZOJ3162】独钓寒江雪(树哈希,动态规划)

    [BZOJ3162]独钓寒江雪(树哈希,动态规划) 题面 BZOJ 题解 忽然翻到这道题目,突然发现就是前几天一道考试题目... 题解: 树哈希,既然只考虑这一棵树,那么,如果两个点为根是同构的, 他 ...

  8. 【BZOJ4540】【HNOI2016】序列(莫队)

    [BZOJ4540][HNOI2016]序列(莫队) 题面 BZOJ 洛谷 Description 给定长度为n的序列:a1,a2,-,an,记为a[1:n].类似地,a[l:r](1≤l≤r≤N)是 ...

  9. 新的JavaScript数据结构Streams

    最近在网上看到了一个新的 Javascript 小程序——Streams,起初以为是一个普通的 Javascript 类库,但读了关于它的介绍后,我发现,这不是一个简单的类库,而且作者的重点也不是这个 ...

  10. OpenCV---Numpy数组的使用以及创建图片

    一:对头像的所有像素进行访问,并UI图像进行像素取反 (一)for循环取反 import cv2 as cv import numpy as np def access_pixels(image): ...