一文搞定hbase coprocessor的使用和管理(部署、卸载以及更新)

620 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Coprocessor简介及使用场景

HBase在使用过程中已经初步具备了RDS中的CRUD的操作,能够满足基础数据的查询和管理。但是这对于常规的RDS使用者以及DBA来说总是感觉少了很多高级功能,诸如触发器以及存储过程等。

而HBase的各路大神显然不打算让RDS的信徒们失望,只有打消了他们的疑虑,meet了他们的需求和习惯,才能推动HBase更快速以及更广泛的发展。在这种背景下,在HBase基础上进行的高级功能扩展就如火如荼的展开了,而Coprocessor就是其中的翘楚与代表。

Coprocessor是协处理器,一种实现类似触发器,AOP和计算本地性等扩展能力的框架。它主要分为两种类型:Observer和Endpoint。

Observer相当于一个监听者,其实现的功能类似于RDS中的触发器;Endpoint相当于一种service,其实现的功能类似于RDS中的存储过程。

触发器和AOP的场景下使用Observer类型;计算本地性和扩展RsGroup都是通过Endpoint的方式来实现。

本文主要围绕Observer来进行讲解。毕竟HBase数据的aop处理以及HBase二级索引的维护都离不开它。

环境信息

HBase 1.2.2,Hadoop 2.7.2

使用需求

由于需要使用es创建hbase的二级索引,所以打算使用coprocessor中的RegionObserver来实现此功能,本文主要也是讲解RegionObserver的加载以及使用注意事项。

使用总结

RegionObserver的加载分为动态加载和静态加载

静态加载(通过配置HBase)

在 hbase-site.xml 定义一个协处理器

<property>
    <name>hbase.coprocessor.region.classes</name>
    <value>org.myname.hbase.coprocessor.endpoint.SumEndPoint</value>
</property>

如果多个类被指定要加载,类名间要用逗号分隔。框架会试图使用默认类加载器加载所有配置的类。因此,这个jar文件必须位于server端的HBase classpath中。

通过这种方式加载的Coprocessors将对所有表的所有Region都是激活状态的。这些也被称作 系统协处理器System Coprocessor。首先列出的Coprocessors 将被赋予 Coprocessor.Priority.SYSTEM这样的优先级。

列表中每一个后续的 coprocessor 将会把它的优先级加一(这是在减小它的优先级,因为优先级是按照整数的自然顺序排列的,第一个coprocessor被赋值为coprocessor1,后续后面的数据会递增)。

当调用注册的observer时,框架会按照它们的优先级顺序执行它们的回调方法。

静态卸载:

  • 在hbase-site.xml中删除协处理器的 元素,包括子元素。
  • 重启 HBase.
  • 可选的操作是,从HBase的 lib/ 目录下或者从classpath中移除这个协处理器的 JAR 文件。

动态加载(使用 HBase Shell 或 Java API):

你可以在不重启HBase的情况下,动态加载协处理器。这看起来比静态加载更好,但是动态加载协处理器的是被加载到一张表上,并且只在加载它们的表上可用。由此,动态加载的协处理器有时被称为表协处理器Table Coprocessor

另外,动态加载一个协处理器相当于改变表的结构,表必须下线以加载协处理器。

准备工作

  • 一个JAR coprocessor.jar 包含了这个 Coprocessor 的执行和它所有的依赖(rich package)。
  • 这个JAR 在 HDFS 中可用,如 hdfs://:/path/coprocessor.jar

HBase shell

添加

在HBase Shell中禁用表

hbase(main):001:0> disable 'ais_ori_2022'

hbase(main):002:0> alter 'ais_ori_2022', METHOD => 'table_att''coprocessor' => 'hdfs:///coprocessor/coprocessor4es-0.0.2-SNAPSHOT.jar|com.hxy.main.HbaseDataSyncEsObserver|1001|es_cluster=cetcocean-es,es_type=indexer,es_index=hbase_indexer_alias,es_port=9300,es_host=192.168.10.48'

使用如下命令加载这个 Coprocessor

hbase(main):003:0> enable 'ais_ori_2018'

hbase(main):04:0> describe 'ais_ori_2018'

协处理器框架将试着从协处理器表的属性值中读取类的信息。这个值包含由管道符号(|)分割成的4片信息。

  • 文件路径: 包含 Coprocessor 定义的这个jar文件必须在所有的RegionServer可以读取到的位置上。你可以拷贝这个文件到每一个RegionServer的本地磁盘上,但是推荐把它存放在HDFS上。
  • 类名: Coprocessor的完整类名.
  • 优先级: 一个整数。该框架将确定在同一个钩子上使用优先级注册的所有配置的观察器的执行顺序,并使用优先级。这片信息可以留空不填,这样的话,框架将会赋予一个默认的优先级值。
  • 参数 (可选): 这片信息会传入协处理器的实现类中。这片信息是可选的。

启用这个表

hbase(main):003:0> enable 'ais_ori_2022'

验证协处理器是否被加载

hbase(main):04:0> describe 'ais_ori_2022'

协处理器应该在TABLE_ATTRIBUTES 列出。

卸载

禁用表

hbase(main):001:0> disable 'ais_ori_2022'

hbase(main):002:0> alter 'users', METHOD => 'table_att_unset', NAME => 'coprocessor$1'

修改表以移除coprocessor

hbase(main):002:0> alter 'users', METHOD => 'table_att_unset', NAME => 'coprocessor$1'

启用表

hbase(main):003:0> enable 'ais_ori_2022'

Java API

添加

过程就是修改表结构,其中增加一个属性即可

TableName tableName = TableName.valueOf("ais_ori_2022");
String path = "hdfs://<namenode>:<port>/path/coprocessor.jar";
Configuration conf = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(conf);
Admin admin = connection.getAdmin();
admin.disableTable("ais_ori_2022");
HTableDescriptor hTableDescriptor = new HTableDescriptor("ais_ori_2022");
HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("data"));
columnDesc.setMaxVersions(3);
columnDesc.setBlockCacheEnabled(true);
columnDesc.setBloomFilterType(BloomType.ROW);
columnDesc.setCompressionType(Algorithm.SNAPPY);
hTableDescriptor.addFamily(columnDesc);
hTableDescriptor.setValue("COPROCESSOR$1", path + "|" + RegionObserverExample.class.getCanonicalName() + "|" + Coprocessor.PRIORITY_USER);
admin.modifyTable("ais_ori_2022", hTableDescriptor);
admin.enableTable("ais_ori_2022");

卸载

将刚才添加的属性去掉覆盖即可

TableName tableName = TableName.valueOf("ais_ori_2022");
String path = "hdfs://<namenode>:<port>/path/coprocessor.jar";
Configuration conf = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(conf);
Admin admin = connection.getAdmin();
admin.disableTable("ais_ori_2022");
HTableDescriptor hTableDescriptor = new HTableDescriptor("ais_ori_2022");
HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("data"));
columnDesc.setMaxVersions(3);
columnDesc.setBlockCacheEnabled(true);
columnDesc.setBloomFilterType(BloomType.ROW);
columnDesc.setCompressionType(Algorithm.SNAPPY);
hTableDescriptor.addFamily(columnDesc);
admin.modifyTable("ais_ori_2022", hTableDescriptor);
admin.enableTable("ais_ori_2022");

问题总结

捆绑协处理器 Bundling Coprocessors

你可以将一个coprocessor的所有类捆绑到RegionServer的classpath中的单个JAR中,以便进行简单的部署。否则,将所有依赖项放在RegionServer的classpath中,以便在RegionServer启动时加载它们。

更新一个协处理器 Updating a Coprocessor

部署一个给定的coprocessor的新版本并不像禁用它、替换JAR并重新启用协处理器那样简单。这是因为你不能在JVM中重新加载一个类(通过反射来进行加载coprocessor的主类),除非你删除了所有的对它当前的引用。既然当前JVM引用了这个已经存在的协处理器,你必须通过重启RegionServer来重启这个JVM,以替代这个协处理器。这种行为是不可能改变的。(划重点:如果想不重启hbase而更新Coprocessor,可以修改Coprocessor的包名来实现,这样jvm就会重新加载这个类达到更新的目的)

协处理器日志 Coprocessor Logging

Coprocessor框架不提供超出标准Java日志记录的API。Coprocessor的日志存放在Regionserver的日志目录下,在不同的region上执行了Coprocessor,会将日志记录在该region对应的Regionserver的日志目录下

协处理器配置 Coprocessor Configuration

针对写es作为hbase二级索引的场景建议将es的相关配置放在coprocessor配置的第四个参数上,可以做到动态配置,涉及到coprocessor使用外部静态配置的场景可以尝试将配置写在此处然后在代码的configuration对象中获取。

协处理器配置容错配置

由于coprocessor是部署在region上的,所以coprocessor如果抛出异常会影响到regionserver的正常运行,源码如下:

/**
   * This is used by coprocessor hooks which are declared to throw IOException
   * (or its subtypes). For such hooks, we should handle throwable objects
   * depending on the Throwable's type. Those which are instances of
   * IOException should be passed on to the client. This is in conformance with
   * the HBase idiom regarding IOException: that it represents a circumstance
   * that should be passed along to the client for its own handling. For
   * example, a coprocessor that implements access controls would throw a
   * subclass of IOException, such as AccessDeniedException, in its preGet()
   * method to prevent an unauthorized client's performing a Get on a particular
   * table.
   * @param env Coprocessor Environment
   * @param e Throwable object thrown by coprocessor.
   * @exception IOException Exception
   */
  protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
      throws IOException {
    if (e instanceof IOException) {
      throw (IOException)e;
    }
    // If we got here, e is not an IOException. A loaded coprocessor has a
    // fatal bug, and the server (master or regionserver) should remove the
    // faulty coprocessor from its set of active coprocessors. Setting
    // 'hbase.coprocessor.abortonerror' to true will cause abortServer(),
    // which may be useful in development and testing environments where
    // 'failing fast' for error analysis is desired.
    if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
      // server is configured to abort.
      abortServer(env, e);
    } else {
      LOG.error("Removing coprocessor '" + env.toString() + "' from " +
          "environment because it threw:  " + e,e);
      coprocessors.remove(env);
      try {
        shutdown(env);
      } catch (Exception x) {
        LOG.error("Uncaught exception when shutting down coprocessor '"
            + env.toString() + "'", x);
      }
      throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
          "' threw: '" + e + "' and has been removed from the active " +
          "coprocessor set.", e);
    }
  }

所以,如果添加coprocessor的时候打算重启HBase,建议增加配置项来防止协处理器出现错误时导致regionServer挂掉

<property>
      <name>hbase.coprocessor.abortonerror</name>
      <value>false</value>
</property>

文章到这里就结束了,最后路漫漫其修远兮,大数据之路还很漫长。如果想一起大数据的小伙伴,欢迎点赞转发加关注,下次学习不迷路,我们在大数据的路上共同前进!