Hbase作为列族数据库最经常被人诟病的特性包括:无法轻易建立“二级索引”,难以执行求和、计数、排序等操作。比如,在旧版本的(<0.92)Hbase中,统计数据表的总行数,需要使用 Counter 方法,执行一次 MapReduce Job 才能得到。虽然 HBase在数据存储层中集成了 MapReduce,能够有效用于数据表的分布式计算。然而在很多情况下,做一些简单的相加或者聚合计算的时候,如果直接将计算过程放置在 server 端,能够减少通讯开销,从而获得很好的性能提升。
于是,HBase 在 0.92之后引入了协处理器(coprocessors),实现一些激动 人心的新特性:能够轻易建立二次索引、复杂过滤器(谓词下推)以及访问控制等。
协处理器有两种:observer 和 endpoint
observer类似于mysql中的触发器,zookeeper中的监听器,主要的作用是当执行被监听的一个操作的时候可以触发另一个我们需要的操作的依赖关系,比如说监听数据库数据的增删过程,我们可以在hbase数据库插入数据或者删除数据之前或之后进行一系列的操作(ps:有很多微博说的是钩子函数,其实没错,但是便于理解,这里就不引入钩子的概念了)
以HBase0.92 版本为例,它提供了三种观察者接口:
● RegionObserver:提供客户端的数据操纵事件钩子:Get、Put、Delete、Scan 等。
● WALObserver:提供 WAL(写前日志write ahead log)相关操作钩子。
● MasterObserver:提供 DDL-类型的操作钩子,如创建、删除、修改数据表等。
到 0.96 版本又新增一个 RegionServerObserver
以 RegionObserver 为例子(图来源网络,如若侵权,请原作者联系删除):
流程说明:
1、客户端发出 put 请求
2、该请求被分派给合适的 RegionServer 和 region
3、coprocessorHost 拦截该请求,然后在该表上登记的每个RegionObserver 上调用 prePut()
4、如果没有被 prePut()拦截,该请求继续送到 region,然后进行处理
5、region 产生的结果再次被 CoprocessorHost 拦截,调用 postPut()
6、假如没有 postPut()拦截该响应,最终结果被返回给客户端
Endpoint 协处理器类似传统数据库中的存储过程,客户端可以调用这些 Endpoint 协处理器执行一段 Server端代码,并将Server 端代码的结果返回给客户端进一步处理,最常见的用法就是进行聚集操作。
在这里有个概念需要跟大家说一下,叫[谓词下推],大家知道传统型数据库如mysql,在执行聚合函数的时候是将数据从数据库拉取到客户端本地然后再进行聚合操作,这个聚合的动作是发生在客户端的,这样的操作在大数据环境下就显得有点尴尬了,这个过程如果变成数据在哪里计算就在哪里的话这就很完美了,能很好的利用大数据的分布式环境,而[谓词下推]呢就是每个服务器节点计算自己的数据,计算完成后将结果发给客户端进行最终的汇总,这就是endpoint协处理器大致的处理流程
两种协处理器的区别:
observer 类似于 RDBMS 中的触发器,主要在服务端工作
endpoint 类似于 RDBMS 中的存储过程,主要在服务端工作
Observer 允许集群在正常的客户端操作过程中可以有不同的行为表现 Endpoint 允许扩展集群的能力,对客户端应用开放新的运算命令
observer 可以实现权限管理、优先级设置、监控、ddl 控制、二级索引等功能 endpoint 可以实现min、max、avg、sum、distinct、group by 等功能
协处理器的加载方式有两种,我们称之为静态加载方式(Static Load)和动态加载方式 (Dynamic Load)。静态加载的协处理器称之为 System Coprocessor,动态加载的协处理器称 之为 Table Coprocessor
通过修改 hbase-site.xml 这个文件来实现,启动全局 aggregation,能过操纵所有的表上的数据。只需要添加如下代码:
<property>
<name>hbase.coprocessor.user.region.classesname>
<value>org.apache.hadoop.hbase.coprocessor.AggregateImplementationvalue>
property>
为所有 table 加载了一个 cp class,可以用”,”分割加载多个 class
注意: 该方法因为是全局的,所以在实际应用中并不是很多,而另一种方法用的会更多一些
启用表 aggregation,只对特定的表生效。通过 HBase Shell 来实现。
①disable 指定表。hbase> disable ‘table名’;
②添加 aggregation
hbase> alter 'mytable', METHOD => 'table_att','coprocessor'=>
'|org.apache.Hadoop.hbase.coprocessor.AggregateImplementation||'
必要参数解释(两个竖杠分隔符|):
第一段 第一个|之前 jar包位置 hdfs的路径
第二段 jar包的协处理器的权限定名
第三段 协处理器的优先级别,同一个表中添加多个协处理器,优先级问题 使用数字表示,数字越大代表优先级越高
第四段 第二个|之后 代码中程序的参数,没有不传
③重启指定表 hbase> enable ‘table名’
禁用表 disable 'mytable'
卸载 alter 'mytable',METHOD=>'table_att_unset',NAME=>'coprocessor$1'
启用表 enable 'mytable'
在社交类应用中,经常需要快速检索各用户的关注列表,同时,又需要反向检索各种用户的粉丝列表,为了实现这个需求,最佳实践是建立两张互为反向的表:
一个表为正向索引关注表:“user_mingxing”:
Rowkey: a
f1:from b
另一个表为反向索引粉丝表:“mingxing_user”:
Rowkey: b
f1:from a
建表语句:
create ‘user_mingxing’,‘cf1’
create ‘mingxing_user’,‘cf1’
实现效果:往 user_mingxing 表插入一条数据,就会自动往 mingxing_user 表插入一条数据 put ‘user_mingxing’,‘a’,‘cf1:from’,‘b’
put ‘mingxing_user’,‘b’,‘cf1:from’,‘a’
插入一条关注信息时,为了减轻应用端维护反向索引表的负担,可用 Observer 协处理器实现:
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Durability;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
public class SecondryIndex extends BaseRegionObserver{
static Configuration conf = null;
static Connection conn = null;
static HTable table = null;
static {
try {
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
conn = ConnectionFactory.createConnection(conf);
table = (HTable) conn.getTable(TableName.valueOf("mingxing_user"));
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 协处理 触发的时候 调用的方法
* 插入数据之后 返回给客户端之前 拦截插入的数据 进行指定操作
* 向索引表插入数据
* 参数1:上下文对象 环境变量
* 参数2:put对象 协处理器 拦截的put对象
* 插入原始表的数据
* 参数3:生命周期
* 向 user_mingxing
* put "user_mingxing","rk001","info:name","luhan"
*/
@Override
public void postPut(ObserverContext<RegionCoprocessorEnvironment> e,
Put put, WALEdit edit, Durability durability)
throws IOException {
//需要解析对象,原始表对象
byte[] row = put.getRow();
Cell cv = put.get("info".getBytes(), "name".getBytes()).get(0);
byte[] value = cv.getValue();
//数据插入新表
//将对象 rk v 分别封装到 mingixng_user表 rk v
Put newput =new Put(value);
newput.add("info".getBytes(), "fensi".getBytes(), row);
table.put(newput);
table.close();
}
}
hadoop fs -put test.jar /hbaseJar
disable 'user_mingxing'
hbase> alter 'user_mingxing', METHOD =>
'table_att','coprocessor'=>
'hdfs://高可用namenode组名/hbaseJar/test.jar|com.aura.cn.secondaryIndex.SecondryIndex|100|'
hbase> enable 'user_mingxing'
查看表的详细信息:
desc user_mingxing;
会出现以下信息,说明添加成功:
user_mingxing, {TABLE_ATTRIBUTES =>
{coprocessor$1 => 'hdfs://高可用namenode组名/jars/test.jar|com.aura.cn.secondaryIndex.SecondryIndex|100|'}
$1协处理器的编号,从1开始顺序递增,每一个协处理器的唯一标识
检查 mingxing_user(在java代码中指定的) 表数据是否存在