【HBase】协处理器

1,062 阅读6分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

官网:hbase.apache.org/book.html#c…

一、概述

访问 HBase 的方式是使用 scanget 获取数据, 在获取到的数据上进行业务运算。

但是在数据量非常大的时候, 比如一个有上亿行及十万个列的数据集, 再按常用的方式移动获取数据就会遇到性能问题。

客户端也需要有强大的计算能力以及足够的内存来处理这么多的数据。

此时就可以考虑使用 Coprocessor (协处理器)。

将业务运算代码封装到 Coprocessor 中并在 RegionServer 上运行, 即在数据实际存储位置执行, 最后将运算结果返回到客户端。

利用协处理器, 用户可以编写运行在 HBase Server 端的代码。

Hbase Coprocessor 类似以下概念:

  1. 触发器和存储过程

一个 Observer Coprocessor 有些类似于关系型数据库中的触发器, 通过它我们可以在一些事件(如 Get 或是 Scan )发生前后执行特定的代码。 Endpoint Coprocessor 则类似于关系型数据库中的存储过程, 因为它允许在 RegionServer 上直接对它存储的数据进行运算, 而非是在客户端完成运算。

  1. MapReduce

MapReduce 的原则就是将运算移动到数据所处的节点。 Coprocessor 也是按照相同的原则去工作的。

  1. AOP

可以将 Coprocessor 的执行过程视为在传递请求的过程中对请求进行了拦截, 并执行了一些自定义代码。

二、协处理器类型

(1)Observer

协处理器与触发器(trigger)类似: 在一些特定事件发生时回调函数(也被称作钩子函数, hook)被执行。

这些事件包括一些用户产生的事件, 也包括服务器端内部自动产生的事件。

协处理器框架提供的接口如下:

  • RegionObserver: 用户可以用这种的处理器处理数据修改事件, 它们与表的 region 联系紧密。
  • MasterObserver : 可以被用作管理或 DDL 类型的操作, 这些是集群级事件。
  • WALObserver: 提供控制 WAL 的钩子函数

(2)Endpoint

这类协处理器类似传统数据库中的存储过程, 客户端可以调用这些 Endpoint 协处理器在 Regionserver 中执行一段代码, 并将 RegionServer 端执行结果返回给客户端进一步处理。

(3)Endpoint 常见用途

聚合操作

假设需要找出一张表中的最大数据, 即 max 聚合操作, 普通做法就是必须进行全表扫描 , 然后 Client 代码内遍历扫描结果, 并执行求最大值的操作。

这种方式存在的弊端是无法利用底层集群的并发运算能力, 把所有计算都集中到 Client 端执行, 效率低下。

使用 Endpoint Coprocessor, 用户可以将求最大值的代码部署到 HBase RegionServer 端, HBase 会利用集群中多个节点的优势来并发执行求最大值的操作。

也就是在每个 Region 范围内执行求最大值的代码, 将每个 Region 的最大值在 Region Server 端计算出, 仅仅将该 max 值返回给 Client。在 Client 进一步将多个 Region 的最大值汇总进一步找到全局的最大值。

Endpoint Coprocessor 的应用后续可以借助于 Phoenix 非常容易就能实现。

针对 Hbase 数据集进行聚合运算直接使用 SQL 语句就能搞定。

三、Observer 案例

需求:

通过协处理器 Observer 实现 Hbase 当中 t1 表插入数据, 指定的另一张表 t2 也需要插入相对应的数据。

$ create 't1','info'
$ create 't2','info'

hbase(main):024:0> create 't1','info'
0 row(s) in 1.3130 seconds

=> Hbase::Table - t1
hbase(main):025:0> create 't2','info'
0 row(s) in 1.2670 seconds

=> Hbase::Table - t2

(1)实现思路

通过 Observer 协处理器捕捉到 t1 插入数据时, 将数据复制一份并保存到 t2 表中。

(2)开发步骤

  1. 添加依赖
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase-server -->
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-server</artifactId>
  <version>1.3.1</version>
</dependency>
  1. 编写 Observer 协处理器
package com.donaldy.hbase.process;

import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
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;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

/**
 * 重写 prePut 方法,监听到向 t1 表插入数据时, 执行向 t2 表插入数据的代码
 *
 * @author donald
 * @date 2020/08/30
 */
public class MyProcessor extends BaseRegionObserver {

    @Override
    public void prePut(ObserverContext<RegionCoprocessorEnvironment> ce, Put put, WALEdit edit, Durability durability)
            throws IOException {

        // 把自己需要执行的逻辑定义在此处,向t2表插入数据,数据具体是什么内容与Put一样
        final HTableInterface t2 = ce.getEnvironment().getTable(TableName.valueOf("t2"));

        // 解析t1表的插入对象put
        final Cell cell = put.get(Bytes.toBytes("info"), Bytes.toBytes("name")).get(0);

        // table对象.put
        final Put put1 = new Put(put.getRow());
        put1.add(cell);

        // 执行向t2表插入数据
        t2.put(put1);
        t2.close();
    }
}
  1. 打成 Jar 包, 上传 HDFS
# 打成 jar 包,命名为 processor.jar 

[root@linux121 ~]# pwd
/root
[root@linux121 ~]# hadoop fs -mkdir /processor
[root@linux121 ~]# hadoop fs -put processor.jar /processor
  1. 挂载协处理器
# 1. 查看 t1 表
hbase(main):026:0> desc 't1'
Table t1 is ENABLED   
t1
COLUMN FAMILIES DESCRIPTION                                                               
{NAME => 'info', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', COMPRESSION => 'NONE', MIN_VERSIONS => 
'0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}                 
1 row(s) in 0.1930 seconds

# 2. 挂载
hbase(main):028:0> alter 't1',METHOD =>'table_att','Coprocessor'=>'hdfs://linux121:9000/processor/processor.jar|com.donaldy.hbase.process.MyProcessor|1001|'
Updating all regions with the new schema...
1/1 regions updated.
Done.
0 row(s) in 2.3080 seconds


# 再次查看't1'表,
hbase(main):029:0> desc 't1'
Table t1 is ENABLED  
t1, {TABLE_ATTRIBUTES => {coprocessor$1 => 'hdfs://linux121:9000/processor/processor.jar|com.donaldy.hbase.process.MyProcessor|1001|'}                                                               
COLUMN FAMILIES DESCRIPTION                                                               
{NAME => 'info', BLOOMFILTER => 'ROW', VERSIONS => '1', IN_MEMORY => 'false', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', COMPRESSION => 'NONE', MIN_VERSIONS => 
'0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}                 
1 row(s) in 0.0660 seconds

alter 't1', METHOD => 'table_att', 'coprocessor' => ①|②|③|④'

解释下上面各个参数: ①:协处理器 Jar 包路径,要保证所有的 RegionServer 可读取到。也可以是本地路径,不过建议是放在 HDFS 上。 ②:协处理器的完整类名。 ③:协处理器优先级,整数表示。可以为空。 ④:传递给协处理器的参数,可以为空。 注:各参数间不要有空格。

  1. 验证协处理器

t1 表中插入数据( shell 方式验证)

hbase(main):009:0> put 't1', 'rk1', 'info:name', 'sasa'
0 row(s) in 0.1520 seconds

hbase(main):009:0> scan 't1'
ROW                                                COLUMN+CELL         
 rk1                                               column=info:name, timestamp=1598716799095, value=sasa                                                                                             
1 row(s) in 0.0690 seconds

hbase(main):010:0> scan 't2'
ROW                                                COLUMN+CELL  
 rk1                                               column=info:name, timestamp=1598716799312, value=sasa                                                                                             
1 row(s) in 0.0590 seconds
  1. 卸载协处理器
disable 't1'
alter 't1',METHOD=>'table_att_unset',NAME=>'coprocessor$1'
enable 't2'

若运行中出现问题,比如 scan 't1' 时,出现超时,查看日志:logs/log 文件

比如查看 logs/hbase-root-regionserver-linux121.log

2020-08-29 23:22:18,963 INFO  [RpcServer.FifoWFPBQ.default.handler=29,queue=2,port=16020] regionserver.HRegionServer: STOPPED: The coprocessor com.donaldy.hbase.process.MyProcessor threw java.lang.ClassCastException: org.apache.hadoop.hbase.client.HTableWrapper cannot be cast to org.apache.hadoop.hbase.client.HTable
2020-08-29 23:22:18,963 INFO  [regionserver/linux121/172.16.64.121:16020] regionserver.SplitLogWorker: Sending interrupt to stop the worker thread
2020-08-29 23:22:18,964 INFO  [regionserver/linux121/172.16.64.121:16020] regionserver.HRegionServer: Stopping infoServer
2020-08-29 23:22:18,986 INFO  [SplitLogWorker-linux121:16020] regionserver.SplitLogWorker: SplitLogWorker interrupted. Exiting. 
2020-08-29 23:22:18,986 INFO  [SplitLogWorker-linux121:16020] regionserver.SplitLogWorker: SplitLogWorker linux121,16020,1598712218688 exiting
2020-08-29 23:22:19,021 INFO  [regionserver/linux121/172.16.64.121:16020] mortbay.log: Stopped SelectChannelConnector@0.0.0.0:16030
2020-08-29 23:22:19,022 INFO  [regionserver/linux121/172.16.64.121:16020] regionserver.HeapMemoryManager: Stopping HeapMemoryTuner chore.
2020-08-29 23:22:19,022 INFO  [regionserver/linux121/172.16.64.121:16020] flush.RegionServerFlushTableProcedureManager: Stopping region server flush procedure manager abruptly.
2020-08-29 23:22:19,023 INFO  [regionserver/linux121/172.16.64.121:16020] snapshot.RegionServerSnapshotManager: Stopping RegionServerSnapshotManager abruptly.
2020-08-29 23:22:19,023 INFO  [MemStoreFlusher.0] regionserver.MemStoreFlusher: MemStoreFlusher.0 exiting
2020-08-29 23:22:19,023 INFO  [MemStoreFlusher.1] regionserver.MemStoreFlusher: MemStoreFlusher.1 exiting