大数据数据库之HBase

1. HBase协处理器

  • http://hbase.apache.org/book.html#cp
  • 起源:
    • Hbase 作为列族数据库最经常被人诟病的特性包括:无法轻易建立“二级索引”,难以执行求和、计数、排序等操作。比如,在旧版本的(<0.92)Hbase 中,统计数据表的总行数,需 要使用 Counter 方法,执行一次 MapReduce Job 才能得到。
    • 虽然 HBase 在数据存储层中集成了 MapReduce,能够有效用于数据表的分布式计算。然而在很多情况下,做一些简单的相加或者聚合计算的时候, 如果直接将计算过程放置在 server端,能够减少通讯开销,从而获得很好的性能提升。
    • 于是, HBase 在 0.92 之后引入了协处理器(coprocessors),实现一些激动人心的新特性:能够轻易建立二次索引、复杂过滤器(谓词下推)以及访问控制等。

两种协处理器

observer

  • Observer 类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。
  • Observer Coprocessor就是一些散布在 HBase Server 端代码中的 hook 钩子, 在固定的事件发生时被调用。

    • 比如: put 操作之前有钩子函数 prePut,该函数在put操作执行前会被Region Server调用;在 put 操作之后则有 postPut 钩子函数
  • 以 HBase0.92 版本为例,它提供了三种观察者接口:

    • RegionObserver:提供客户端的数据操纵事件钩子: Get、 Put、 Delete、 Scan 等。
    • WALObserver:提供 WAL 相关操作钩子。
    • MasterObserver:提供 DDL类型的操作钩子。如创建、删除、修改数据表等。
    • 到 0.96 版本又新增一个 RegionServerObserver

  • 下图是以 RegionObserver 为例子讲解 Observer 这种协处理器的原理:

1122015-20170511100919222-711579099

endpoint

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

  • 最常见的用法就是进行聚集操作。

    • 如果没有协处理器,当用户需要找出一张表中的最大数据,即max 聚合操作,就必须进行全表扫描,在客户端代码内遍历扫描结果,并执行求最大值的操作。
    • 这样的方法无法利用底层集群的并发能力,而将所有计算都集中到 Client 端统一执 行,势必效率低下。
    • 利用 Coprocessor,用户可以将求最大值的代码部署到 HBase Server 端,HBase将利用底层cluster 的多个节点并发执行求最大值的操作。即在每个 Region范围内执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出,仅仅将该 max 值返回给客户端。
    • 在客户端进一步将多个 Region 的最大值进一步处理而找到其中的最大值。这样整体的执行效率就会提高很多
    - Observer允许集群在正常的客户端操作过程中可以有不同的行为表现
    - Endpoint 允许扩展集群的能力,对客户端应用开放新的运算命令
    - observer 类似于 RDBMS 中的触发器,主要在服务端工作
    - endpoint 类似于 RDBMS 中的存储过程,主要在 client 端工作
    - observer 可以实现权限管理、优先级设置、监控、 ddl 控制、 二级索引等功能
    - endpoint 可以实现 min、 max、 avg、 sum、 distinct、 group by 等功能
    

加载方式

  • 协处理器的加载方式有两种
    • 静态加载方式( Static Load);静态加载的协处理器称之为 System Coprocessor
    • 动态加载方式 ( Dynamic Load);动态加载的协处理器称 之为 Table Coprocessor

静态加载

  • 通过修改 hbase-site.xml 这个文件来实现, 如启动全局 aggregation,能过操纵所有的表数据。只需要在hbase-site.xml里面添加以下配置即可
  • ==注意==:修改完配置之后需要重启HBase集群
<property>
    <name>hbase.coprocessor.user.region.classes</name>
    <value>org.apache.hadoop.hbase.coprocessor.AggregateImplementation</value>
</property>
  • 为所有table加载了一个 cp class,可以用” ,”分割加载多个 class,修改

动态加载

  • 启用表aggregation,只对特定的表生效。
# disable 指定表。
hbase> disable 'mytable'
# 添加 aggregation
hbase> alter 'mytable', METHOD => 'table_att','coprocessor'=>'|org.apache.Hadoop.hbase.coprocessor.AggregateImplementation||'
# 重启指定表 
hbase> enable 'mytable'

# 协处理器的卸载
disable 'mytable'
alter  'mytable',METHOD=>'table_att_unset',NAME='coprocessor$1'
enable  'mytable'

协处理器示例

xdfsdfsdf

  • 通过协处理器Observer实现向hbase当中一张表插入数据时,通过协处理器,将数据复制一份保存到另外一张表当中去;但是只取第一张表当中的部分列数据,保存到第二张表当中去

  • 打开hbase shell

# 1. 创建一张表
create 'proc1','info'

# 2. 创建第二张表
create 'proc2','info'
  • HBase协处理器
<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-client</artifactId>
  <version>2.6.0-mr1-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-client</artifactId>
  <version>1.2.0-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-server</artifactId>
  <version>1.2.0-cdh5.14.2</version>
</dependency>
  • 开发HBase的协处理器
public class MyProcessor extends BaseRegionObserver {
    /**
     * @param put   插入到proc1表里面的数据,都是封装在put对象里面了,就可以解析put对象,获取数据,获取到了数据之后,插入到proc2表里面去
     */
    @Override
    public void prePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability) throws IOException {
        // 从put获取到数据,直接开启HBase连接插入到第二张表。
      // TODO
    }
}
# 1. jar包名称必须为processor.jar
mv original-hbaseStudy-1.0-SNAPSHOT.jar  processor.jar
# 2. 上传到hdfs的/processor目录即可
hdfs dfs -mkdir -p /processor
hdfs dfs -put processor.jar /processor

# 加载协处理器
describe 'proc1'
alter 'observer:source',METHOD => 'table_att','Coprocessor'=>'hdfs://node01:8020/processor/processor.jar|top.kfly.hbasemr.MyProcessor|1001|'
# 查看
describe 'proc1'

rowkey设计

  • 长度原则

    • rowkey是一个二进制码流,可以是任意字符串,最大长度64kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。

    • 建议尽可能短;但是也不能太短,否则rowkey前缀重复的概率增大

    • 设计过长会降低memstore内存的利用率和HFile存储数据的效率。
  • 散列原则

    • 建议将rowkey的高位作为散列字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。
    • 如果没有散列字段,首字段直接是时间信息。所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。
  • 唯一原则

    • 必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的
    • 因此,设计rowkey的时候,要充分利用这个排序的特点,可以将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块
    • 电信上网详单数据,就是保存在HBase中的

2019-10-16_112336

HBase表的热点

- 检索habse的记录首先要通过row key来定位数据行。
- 当大量的client访问hbase集群的一个或少数几个节点,造成少数region server的读/写请求过多、负载过大,而其他region server负载却很小,就造成了“热点”现象。
  • 热点的解决方案

  • 预分区

    • 预分区的目的让表的数据可以均衡的分散在集群中,而不是默认只有一个region分布在集群的一个节点上。
  • 加盐

  • 这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同

  • 哈希

    • 哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据。

      rowkey=MD5(username).subString(0,10)+时间戳    
      

      ~~~markdown

  • 反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。
  • 这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。
  • eg: 手机号码的反转: 0120xxxx751
  • eg: rowkey= address + age + 随机数
    ~~~

HBase的数据备份

  • 基于HBase提供的类对表进行备份
# 使用HBase提供的类把HBase中某张表的数据导出到HDFS,之后再导出到测试hbase表中。
hbase org.apache.hadoop.hbase.mapreduce.Export myuser /hbase_data/myuser_bak

# hbase shell中创建备份目标表
create 'myuser_bak','f1','f2'

# 将HDFS上的数据导入到备份目标表中
hbase org.apache.hadoop.hbase.mapreduce.Driver import myuser_bak /hbase_data/myuser_bak/*

# 增量备份
hbase org.apache.hadoop.hbase.mapreduce.Export test /hbase_data/test_bak_increment 开始时间戳  结束时间戳
  • 基于snapshot快照对表进行备份

    • 通过snapshot快照的方式实现HBase数据的迁移和拷贝。这种方式比较常用,效率高,也是最为推荐的数据迁移方式。

    • HBase的snapshot其实就是一组==metadata==信息的集合(文件列表),通过这些metadata信息的集合,就能将表的数据回滚到snapshot那个时刻的数据。

      • 首先我们要了解一下所谓的HBase的LSM类型的系统结构,我们知道在HBase中,数据是先写入到Memstore中,当Memstore中的数据达到一定条件,就会flush到HDFS中,形成HFile,后面就不允许原地修改或者删除了。
      • 如果要更新或者删除的话,只能追加写入新文件。既然数据写入以后就不会在发生原地修改或者删除,这就是snapshot做文章的地方。做snapshot的时候,只需要给快照表对应的所有文件创建好指针(元数据集合),恢复的时候只需要根据这些指针找到对应的文件进行恢复就Ok。这是原理的最简单的描述,下图是描述快照时候的简单流程:

    snapshot

-- 创建表的snapshot
snapshot 'tableName', 'snapshotName'
-- 查看快照
list_snapshots  
-- 查看以test开头的快照
list_snapshots 'test.*'
-- 恢复快照
disable 'tableName'
restore_snapshot 'snapshotName'
enable 'tableName'
-- 删除快照
delete_snapshot 'snapshotName'
-- 迁移 snapshot
hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot \
  -snapshot snapshotName  \
  -copy-from hdfs://src-hbase-root-dir/hbase \
  -copy-to hdfs://dst-hbase-root-dir/hbase \
  -mappers 1 \
  -bandwidth 1024

  -- 用于将快照迁移到另外一个集群
  hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot \
  -snapshot test  \
  -copy-from hdfs://node01:8020/hbase \
  -copy-to hdfs://kfly01:8020/hbase1 \
  -mappers 1 \
  -bandwidth 1024
  -- 使用的时候记得设置好bandwidth参数,以免由于网络打满导致的线上业务故障。
  • 将snapshot使用bulkload的方式导入

    ~~~shell
    hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles \
    hdfs://dst-hbase-root-dir/hbase/archive/datapath/tablename/filename \
    tablename

– 创建一个新表
create ‘newTest’,’f1’,’f2’
hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles hdfs://node1:9000/hbase1/archive/data/default/test/6325fabb429bf45c5dcbbe672225f1fb newTest
~~~

HBase二级索引

hbase寻址

  • HBase表后期按照rowkey查询性能是最高的。rowkey就相当于hbase表的一级索引
  • 但是在实际的工作中,我们做的查询基本上都是按照一定的条件进行查找,无法事先知道满足这些条件的rowkey是什么,正常是可以通过hbase过滤器去实现。但是效率非常低,这是由于查询的过程中需要在底层进行大量的文件扫描。

  • HBase的二级索引

  • 为了HBase的数据查询更高效、适应更多的场景,诸如使用非rowkey字段检索也能做到秒级响应,或者支持各个字段进行模糊查询和多字段组合查询等, 因此需要在HBase上面构建二级索引, 以满足现实中更复杂多样的业务需求。
    • hbase的二级索引其本质就是建立HBase表中列与行键之间的映射关系。

  • 构建hbase二级索引方案
    • MapReduce方案
    • Hbase Coprocessor(协处理器)方案
    • Solr+hbase方案
    • ES+hbase方案
    • Phoenix+hbase方案

HBase的namespace

  • namespace基本介绍

    • 在HBase中,namespace命名空间指对一组表的逻辑分组,类似RDBMS中的database,方便对表在业务上划分。
    • Apache HBase从0.98.0, 0.95.2两个版本号开始支持namespace级别的授权操作,HBase全局管理员能够创建、改动和回收namespace的授权。
  • namespace的作用

    • 配额管理:限制一个namespace可以使用的资源,包括region和table
    • 命名空间安全管理:提供了另一个层面的多租户安全管理

    • Region服务器组:一个命名或一张表,可以被固定到一组RegionServers上,从而保证了数据隔离性

  • namespace的基本操作

创建namespace
hbase>create_namespace 'nametest'  

查看namespace
hbase>describe_namespace 'nametest'  

列出所有namespace
hbase>list_namespace  

在namespace下创建表
hbase>create 'nametest:testtable', 'fm1' 

查看namespace下的表
hbase>list_namespace_tables 'nametest'  

删除namespace
hbase>drop_namespace 'nametest'  

数据版本的确界以及TTL

数据的确界

  • 在HBase当中,我们可以为数据设置上界和下界,其实就是定义数据的历史版本保留多少个,通过自定义历史版本保存的数量,我们可以实现数据多个历史版本的数据查询

  • 版本的下界

    • 默认的版本下界是0,即禁用。row版本使用的最小数目是与生存时间(TTL Time To Live)相结合的,并且我们根据实际需求可以有0或更多的版本,使用0,即只有1个版本的值写入cell。
  • 版本的上界

    • 之前默认的版本上界是3,也就是一个row保留3个副本(基于时间戳的插入)。
    • 该值不要设计的过大,一般的业务不会超过100。如果cell中存储的数据版本号超过了3个,再次插入数据时,最新的值会将最老的值覆盖。(现版本已默认为1)

数据的TTL

  • 在实际工作当中经常会遇到有些数据过了一段时间我们可能就不需要了,那么这时候我们可以使用定时任务去定时的删除这些数据
  • 或者我们也可以使用Hbase的TTL(Time To Live)功能,让我们的数据定期的会进行清除

  • 使用代码来设置数据的确界以及设置数据的TTL如下

<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-client</artifactId>
  <version>2.6.0-mr1-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-client</artifactId>
  <version>1.2.0-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-server</artifactId>
  <version>1.2.0-cdh5.14.2</version>
</dependency>
// columa family
HColumnDescriptor column = new HColumnDescriptor("f1");
// version
column.setMaxVersions(5);
column.setMinVersions(3);
// ttl unit s
column.setTimeToLive(30);