HBase基础

HBase简介

HBase的概念

HBase是⼀个分布式海量列式⾮关系型数据库系统,可以提供超⼤规模数据集的实时随机读写。

什么是列式存储?举个例子:

MySQL是行式存储,需要存储如下数据:

id name age salary
1 小明 21
2 小红 12k

这样存储信息,空值字段会浪费存储空间

但如果是列式存储:

1
2
3
4
rowkey: 1 name: 小明
rowkey: 1 age: 21
rowkey: 2 name: 小红
rowkey: 2 salary: 12k

HBase的优点

  • 海量存储: 底层基于HDFS存储海量数据
  • 列式存储:HBase表的数据是基于列族进⾏存储的,⼀个列族包含若⼲列
  • 极易扩展:底层依赖HDFS,当磁盘空间不⾜的时候,只需要动态增加DataNode服务节点就可以
  • ⾼并发:⽀持⾼并发的读写请求
  • 稀疏:稀疏主要是针对HBase列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占⽤存储空间的
  • 数据的多版本:HBase表中的数据可以有多个版本值,默认情况下是根据版本号去区分,版本号就是插⼊数据的时间戳
  • 数据类型单⼀:所有的数据在HBase中是以字节数组进⾏存储

HBase数据模型

一些概念:

  • NameSpace,命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。HBase两个⾃带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,default表是⽤户默认使⽤的命名空间。⼀个表可以⾃由选择是否有命名空间,如果创建表的时候加上了命名空间后,这个表名字以:作为区分
  • Table,类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,数据属性,⽐如超时时间(TTL),压缩算法(COMPRESSION)等,都在列族的定义中定义,不需要声明具体的列。
  • Row(一行逻辑数据),HBase表中的每⾏数据都由⼀个RowKey和多个Column(列)组成。⼀个⾏包含了多个列,这些列通过列族来分类,⾏中的数据所属列族只能从该表所定义的列族中选取,不能定义这个表中不存在的列族,否则报错NoSuchColumnFamilyException。
  • RowKey(每行数据主键),Rowkey由⽤户指定的⼀串不重复的字符串定义,是⼀⾏的唯⼀标识。数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进⾏检索,所以RowKey的设计⼗分重要。如果使⽤了之前已经定义的RowKey,那么会将之前的数据更新掉。
  • Column Family(列族),列族是多个列的集合。⼀个列族可以动态地灵活定义多个列。表的相关属性⼤部分都定义在列族上,同⼀个表⾥的不同列族可以有完全不同的属性配置,但是同⼀个列族内的所有列都会有相同的属性。列族存在的意义是HBase会把相同列族的列尽量放在同⼀台机器上,所以说,如果想让某⼏个列被放到⼀起,你就给他们定义相同的列族。
  • Column Qualifier(列),Hbase中的列是可以随意定义的,⼀个⾏中的列不限名字、不限数量,只限定列族。因此列必须依赖于列族存在!列的名称前必须带着其所属的列族!例如info:name,info:age
  • TimeStamp(时间戳=>版本),⽤于标识数据的不同版本(version)。时间戳默认由系统指定,也可以由⽤户显式指定。在读取单元格的数据时,版本号可以省略,如果不指定,Hbase默认会获取最后⼀个版本的数据返回!
  • Cell,⼀个列中可以存储多个版本的数据。⽽每个版本就称为⼀个单元格(Cell)。
  • Region(表的分区),Region由⼀个表的若⼲⾏组成!在Region中⾏的排序按照⾏键(rowkey)字典排序。Region不能跨RegionSever,且当数据量⼤的时候, HBase会拆分Region。

HBase的逻辑架构:

HBase物理存储:

HBase整体架构

各组件的作用:

  • zookeeper
    • 实现了HMaster的⾼可⽤
    • 保存了HBase的元数据信息,是所有HBase表的寻址⼊⼝
    • 对HMaster和HRegionServer实现了监控
  • HMaster(Master)
    • 为HRegionServer分配Region
    • 维护整个集群的负载均衡
    • 维护集群的元数据信息
    • 发现失效的Region,并将失效的Region分配到正常的HRegionServer上
  • HRegionServer(RegionServer)
    • 负责管理Region
    • 接受客户端的读写数据请求
    • 切分在运⾏过程中变⼤的Region
  • Region
    • 每个HRegion由多个Store构成
    • 每个Store保存⼀个列族(Columns Family),表有⼏个列族,则有⼏个Store
    • 每个Store由⼀个MemStore和多个StoreFile组成,MemStore是Store在内存中的内容,写到⽂件后就是StoreFile。StoreFile底层是以HFile的格式保存

HBase的安装部署

下载安装包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 解压安装包
tar -zxvf hbase-1.3.1-bin.tar.gz -C /opt/servers

# 配置hbase的环境变量
export HBASE_HOME=/opt/servers/hbase-1.3.1
export PATH=$PATH:$HBASE_HOME/bin

# 需要把hadoop中的配置core-site.xml 、hdfs-site.xml拷⻉到hbase安装⽬录下的conf⽂件夹中
ln -s /opt/servers/hadoop-2.9.2/etc/hadoop/core-site.xml /opt/servers/hbase-1.3.1/conf/core-site.xml
ln -s /opt/servers/hadoop-2.9.2/etc/hadoop/hdfs-site.xml /opt/servers/hbase-1.3.1/conf/hdfs-site.xml

# 修改 hbase-env.sh
#添加java环境变量
export JAVA_HOME=/opt/servers/jdk1.8.0_231
#指定使⽤外部的zk集群
export HBASE_MANAGES_ZK=FALSE

修改 hbase-site.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<configuration>
<!-- 指定hbase在HDFS上存储的路径 -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://node1:9000/hbase</value>
</property>
<!-- 指定hbase是分布式的 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 指定zk的地址,多个⽤“,”分割 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>node1:2181,node2:2181,node3:2181</value>
</property>
</configuration>

修改regionservers⽂件

1
2
3
4
5
6
7
8
9
10
vim regionservers

node1
node2
node3

# hbase的conf⽬录下创建⽂件backup-masters (Standby Master)
vim backup-masters

hadoop-node2

将hbase目录分发到其他节点

HBase集群的启动和停⽌:

  • 前提条件:先启动hadoop和zk集群
  • 启动HBase:start-hbase.sh
  • 停⽌HBase:stop-hbase.sh
  • 启动好HBase集群之后,可以访问地址:HMaster的主机名:16010,查看HBase集群的web管理界面

HBase shell的基本操作

进⼊Hbase客户端命令操作界⾯

1
hbase shell

查看帮助命令:

1
hbase(main):001:0> help

查看当前数据库中有哪些表

1
hbase(main):002:0> list

创建⼀张student表, 包含base_info、extra_info两个列族

1
2
3
4
hbase(main):004:0> create 'student','base_info','extra_info'
或者
create 'student', {NAME => 'base_info', VERSIONS => '3'},{NAME => 'extra_info',VERSIONS => '3'}
# VERSIONS 是指此单元格内的数据可以保留最近的 3 个版本

添加数据操作

1
2
3
4
5
6
7
8
# 向student表插入信息,row key为 rk1,列族base_info中添加name列标示符,值为wang
hbase(main):006:0> put "student", "rk1", "base_info:name", "wang"

# 向student表中插⼊信息,row key为rk1,列族base_info中添加age列标示符,值为30
hbase(main):008:0> put 'student', 'rk1', 'base_info:age', 30

# 向student表中插⼊信息,row key为rk1,列族extra_info中添加address列标示符,值为shanghai
hbase(main):010:0> put 'student', 'rk1', 'extra_info:address', 'shanghai'

查询数据

通过rowkey进⾏查询

1
2
3
4
5
6
7
8
# 获取表中row key为rk1的所有信息
hbase(main):012:0> get 'student', 'rk1'
COLUMN CELL
base_info:age timestamp=1622014015143, value=30
base_info:name timestamp=1622013932351, value=wang
extra_info:address timestamp=1622014075602, value=shanghai
1 row(s) in 0.0270 seconds

查看rowkey下⾯的某个列族的信息

1
2
3
4
5
6
# 获取student表中row key为rk1,base_info列族的所有信息
hbase(main):013:0> get 'student', 'rk1', 'base_info'
COLUMN CELL
base_info:age timestamp=1622014015143, value=30
base_info:name timestamp=1622013932351, value=wang
1 row(s) in 0.0080 seconds

查看rowkey指定列族指定字段的值

1
2
3
4
5
6
# 获取表中row key为rk1,base_info列族的name、age列标示符的信息
hbase(main):014:0> get 'student', 'rk1', 'base_info:name', 'base_info:age'
COLUMN CELL
base_info:age timestamp=1622014015143, value=30
base_info:name timestamp=1622013932351, value=wang
1 row(s) in 0.0040 seconds

查看rowkey指定多个列族的信息

1
2
3
4
5
6
# 获取表中row key为rk1,base_info、extra_info列族的信息
hbase(main):010:0> get 'student', 'rk1', 'base_info', 'extra_info'
或者
hbase(main):011:0> get 'student', 'rk1', {COLUMN => ['base_info', 'extra_info']}
或者
hbase(main):012:0> get 'student', 'rk1', {COLUMN => ['base_info:name', 'extra_info:address']}

指定rowkey与列值查询

1
2
3
4
5
# 获取表中row key为rk1,cell的值为wang的信息
hbase(main):029:0> get 'student', 'rk1', {FILTER => "ValueFilter(=, 'binary:wang')"}
COLUMN CELL
base_info:name timestamp=1622013932351, value=wang
1 row(s) in 0.0030 second

指定rowkey与列值模糊查询

1
2
3
4
5
# 获取表中row key为rk1,列标示符中含有d的信息
hbase(main):033:0> get 'student', 'rk1', {FILTER => "(QualifierFilter(=,'substring:d'))"}
COLUMN CELL
extra_info:address timestamp=1622014075602, value=shanghai
1 row(s) in 0.0030 second

查询所有数据

1
2
3
4
5
6
7
hbase(main):034:0> scan 'student'
ROW COLUMN+CELL
rk1 column=base_info:age, timestamp=1622014015143, value=30
rk1 column=base_info:name, timestamp=1622013932351, value=wang
rk1 column=extra_info:address, timestamp=1622014075602, value=shangh
ai
1 row(s) in 0.0190 seconds

列族查询

1
2
3
4
5
6
# 查询表中列族为 base_info 的信息
hbase(main):001:0> scan 'student', {COLUMNS => 'base_info'}

hbase(main):002:0> scan 'student', {COLUMNS => 'base_info', RAW => true, VERSIONS => 3}
## Scan时可以设置是否开启Raw模式,开启Raw模式会返回包括已添加删除标记但是未实际删除的数据
## VERSIONS指定查询的最⼤版本数

指定多个列族与按照数据值模糊查询

1
2
3
4
5
6
7
# 查询表中列族为 base_info 和 extra_info且列标示符中含有a字符的信息
hbase(main):039:0> scan 'student', {COLUMNS => ['base_info', 'extra_info'], FILTER => "(QualifierFilter(=,'substring:a'))"}
ROW COLUMN+CELL
rk1 column=base_info:age, timestamp=1622014015143, value=30
rk1 column=base_info:name, timestamp=1622013932351, value=wang
rk1 column=extra_info:address, timestamp=1622014075602, value=shanghai
1 row(s) in 0.0070 seconds

rowkey的范围值查询

1
2
3
4
5
6
# 查询表中列族为base_info,rk范围是[rk1, rk3)的数据(rowkey底层存储是字典序)
hbase(main):043:0> scan 'student', {COLUMNS => 'base_info', STARTROW => 'rk1', ENDROW => 'rk3'}
ROW COLUMN+CELL
rk1 column=base_info:age, timestamp=1622014015143, value=30
rk1 column=base_info:name, timestamp=1622013932351, value=wang
1 row(s) in 0.0040 seconds

指定rowkey模糊查询

1
2
3
4
5
6
7
8
# 查询student表中row key以rk字符开头的
hbase(main):045:0> scan 'student',{FILTER=>"PrefixFilter('rk')"}
ROW COLUMN+CELL
rk1 column=base_info:age, timestamp=1622014015143, value=30
rk1 column=base_info:name, timestamp=1622013932351, value=wang
rk1 column=extra_info:address, timestamp=1622014075602, value=shangh
ai
1 row(s) in 0.0070 seconds

更新数据

更新操作同插⼊操作⼀模⼀样,只不过有数据就更新,没数据就添加

把student表中rowkey为rk1的base_info列族下的列name修改为zhangsan

1
hbase(main):046:0> put 'student', 'rk1', 'base_info:name', 'zhangsan'

删除数据和表

指定rowkey以及列名进⾏删除

1
2
# 删除student表row key为rk1,列标示符为 base_info:name 的数据
hbase(main):048:0> delete 'student', 'rk1', 'base_info:name'

删除 base_info 列族

1
2
3
4
5
hbase(main):050:0> alter 'student', 'delete' => 'base_info'
Updating all regions with the new schema...
1/1 regions updated.
Done.
0 row(s) in 2.1990 seconds

清空表数据

1
2
3
4
5
hbase(main):052:0> truncate 'student'
Truncating 'student' table (it may take a while):
- Disabling table...
- Truncating table...
0 row(s) in 3.3660 seconds

删除表

1
2
3
4
5
6
7
8
#先disable 再drop
hbase(main):054:0> disable 'student'
0 row(s) in 2.2270 seconds

hbase(main):055:0> drop 'student'
0 row(s) in 1.2350 seconds

#如果不进⾏disable,直接drop会报错

HBase内部原理

HBase读数据流程

  • client⾸先访问zookeeper找到meta表的region位置,然后读取meta表中的数据,meta表中存储了⽤户表的region信息
  • 根据要查询的namespace、表名和rowkey信息。找到写⼊数据对应的region信息
  • 找到这个region对应的regionServer,然后发送请求
  • 查找对应的region
  • 先从MemStore查找数据,如果没有,再从BlockCache上读取
  • 如果BlockCache中也没有找到,再到StoreFile上进⾏读取
  • 从storeFile中读取到数据之后,不是直接把结果数据返回给客户端, ⽽是把数据先写⼊到BlockCache中,⽬的是为了加快后续的查询;然后在返回结果给客户端

HBase上Regionserver的内存分为两个部分:

  • ⼀部分作为Memstore,主要⽤来写
  • 另外⼀部分作为BlockCache,主要⽤于读数据

HBase写数据流程

  • client⾸先访问zookeeper找到meta表的region位置,然后读取meta表中的数据,meta表中存储了⽤户表的region信息
  • 根据要查询的namespace、表名和rowkey信息。找到写⼊数据对应的region信息
  • 找到这个region对应的regionServer,然后发送请求
  • 把数据分别写到HLog(write ahead log)和memstore各⼀份
  • memstore达到阈值后把数据刷到磁盘,⽣成storeFile⽂件
  • 删除HLog中的历史数据

HBase的flush机制

flush机制

  • 当memstore的⼤⼩超过这个值的时候,会flush到磁盘,默认为128M
1
2
3
4
<property>
<name>hbase.hregion.memstore.flush.size</name>
<value>134217728</value>
</property>
  • 当memstore中的数据时间超过1⼩时,会flush到磁
1
2
3
4
<property>
<name>hbase.regionserver.optionalcacheflushinterval</name>
<value>3600000</value>
</property>
  • HregionServer的全局memstore的⼤⼩,超过该⼤⼩会触发flush到磁盘的操作,默认是堆⼤⼩的 40% * 0.95
1
2
3
4
5
6
7
8
<property>
<name>hbase.regionserver.global.memstore.size</name>
<value>0.4</value>
</property>
<property>
<name>hbase.regionserver.global.memstore.size.lower.limit</name>
<value>0.95</value>
</property>
  • ⼿动flush
1
flush tableName

阻塞机制

上面说的是Store中memstore数据刷写磁盘的标准,但是Hbase会进行周期性检查,看是否满⾜以上标准,满⾜就会进⾏刷写。

但是如果在下次检查到来之前,数据疯狂写⼊Memstore中,会触发阻塞机制,此时⽆法写⼊数据到Memstore,数据⽆法写⼊Hbase集群。

  • 当memstore达到128M时,没有检查会继续往里写,刷写磁盘不会阻塞,但是数据量远远大于128M时,hbase为了保证集群的安全,会停止写入数据。这个停止写入数据的数据量大小默认是512M
1
2
3
memstore数据达到一定值的计算公式:hbase.hregion.memstore.flush.size * hbase.hregion.memstore..block.multiplier
hbase.hregion.memstore.flush.size刷写的阀值,默认是134217728,即128MB
hbase.hregion.memstore.block.multiplier是⼀个倍数,默认是4
  • regionserver全部的memstore达到规定值,这个规定值是可以配置的
1
2
3
4
5
hbase.regionserver.global.memstore.size.lower.limit是0.95,
hbase.regionserver.global.memstore.size是0.4,
堆内存总共是 16G,
触发刷写的阈值是:16*0.95*0.4=6.08GB
触发阻塞的阈值是:16*0.4=6.4GB

hbase阻塞了是无法写入数据的,就无法使用了。缓解阻塞机制:可以尽可能的调大内存

Compact合并机制

在hbase中主要存在两种类型的compact合并

minor compact 小合并

  • 在将Store中多个HFile(StoreFile)合并为⼀个HFile。这个过程中,删除和更新的数据仅仅只是做了标记,并没有物理移除,这种合并的触发频率很⾼。
  • minor compact⽂件选择标准 由以下⼏个参数共同决定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!--待合并⽂件数据必须⼤于等于下⾯这个值-->
<property>
<name>hbase.hstore.compaction.min</name>
<value>3</value>
</property>

<!--待合并⽂件数据必须⼩于等于下⾯这个值-->
<property>
<name>hbase.hstore.compaction.max</name>
<value>10</value>
</property>

<!--默认值为128m,
表示⽂件⼤⼩⼩于该值的store file ⼀定会加⼊到minor compaction的store file中
-->
<property>
<name>hbase.hstore.compaction.min.size</name>
<value>134217728</value>
</property>

<!--默认值为LONG.MAX_VALUE,
表示⽂件⼤⼩⼤于该值的store file ⼀定会被minor compaction排除-->
<property>
<name>hbase.hstore.compaction.max.size</name>
<value>9223372036854775807</value>
</property>

根据上面的配置来看,一次minor compact最少3个文件最多10个文件

触发minor compact的条件:

  • memstore flush,在进⾏memstore flush前后都会进⾏判断是否触发compact
  • 定期检查线程,周期性检查是否需要进⾏compaction操作,由参数:hbase.server.thread.wakefrequency决定,默认值是10000 millseconds

major compact 大合并

合并Store中所有的HFile为⼀个HFile

这个过程有删除标记的数据会被真正移除,同时超过单元格maxVersion的版本记录也会被删除。合并频率⽐较低,默认7天执⾏⼀次,并且性能消耗⾮常大,建议⽣产关闭(设置为0),在应⽤空闲时间⼿动触发。⼀般可以是⼿动控制进⾏合并,防⽌出现在业务⾼峰期

major compact触发时间条件:

1
2
3
4
5
<!--默认值为7天进⾏⼀次⼤合并,-->
<property>
<name>hbase.hregion.majorcompaction</name>
<value>604800000</value>
</property>

⼿动触发:

1
2
# 使⽤major_compact命令
major_compact tableName

Region 拆分机制

Region中存储的是⼤量的rowkey数据,当Region中的数据条数过多的时候,直接影响查询效率。

当Region过⼤的时候,HBase会拆分Region,这也是Hbase的⼀个优点。

拆分策略

HBase的Region Split策略⼀共有以下⼏种:

  • ConstantSizeRegionSplitPolicy

这是0.94版本前默认切分策略。

当region大小大于某个阈值(hbase.hregion.max.filesize=10G)之后就会触发切分,⼀个region等分为2个region。

但是在⽣产线上这种切分策略却有相当的弊端:切分策略对于大表和小表没有明显的区分。阈值(hbase.hregion.max.filesize)设置较大对大表比较友好,但是小表就有可能不会触发分裂,极端情况下可能就1个,这对业务来说并不是什么好事。如果设置较小则对小表友好,但⼀个大表就会在整个集群产生大量 的region,这对于集群的管理、资源使⽤、failover来说都不是⼀件好事。

  • IncreasingToUpperBoundRegionSplitPolicy

是0.94版本~2.0版本默认切分策略

1
2
3
4
5
6
7
8
9
10
11
切分策略稍微有点复杂,总体看和ConstantSizeRegionSplitPolicy思路相同,⼀个region⼤⼩⼤于设置阈值就会触发切分。但是这个阈值并不像ConstantSizeRegionSplitPolicy是⼀个固定的值,⽽是会在⼀定条件下不断调整,调整规则和region所属表在当前regionserver上的region个数有关系。

region split的计算公式是:
regioncount^3 * 128M * 2,当region达到该size的时候进⾏split

例如:
第⼀次split:1^3 * 256 = 256MB
第⼆次split:2^3 * 256 = 2048MB
第三次split:3^3 * 256 = 6912MB
第四次split:4^3 * 256 = 16384MB > 10GB,因此取较⼩的值10GB
后⾯每次split的size都是10GB了
  • SteppingSplitPolicy

2.0版本默认切分策略

1
2
3
这种切分策略的切分阈值⼜发⽣了变化,相比 IncreasingToUpperBoundRegionSplitPolicy 简单了⼀些,依然和待分裂region所属表在当前regionserver上的region个数有关系,如果region个数等于1,切分阈值为flush size * 2,否则为MaxRegionFileSize。

这种切分策略对于⼤集群中的⼤表、⼩表会⽐ncreasingToUpperBoundRegionSplitPolicy 更加友好,⼩表不会再产⽣⼤量的⼩region,⽽是适可⽽⽌
  • KeyPrefixRegionSplitPolicy

根据rowKey的前缀对数据进行分组,这⾥是指定rowKey的前多少位作为前缀,⽐如rowKey都是16位的,指定前5位是前缀,那么前5位相同的rowKey在进行region split的时候会分到相同的region中。

  • DelimitedKeyPrefixRegionSplitPolicy

保证相同前缀的数据在同⼀个region中,例如rowKey的格式为:userideventtype_eventid,指定的delimiter为 ,则split的的时候会确保userid 相同的数据在同⼀个region中。

  • DisabledRegionSplitPolicy

不启用自动拆分,需要指定⼿动拆分。不建议!

RegionSplitPolicy的应用

Region拆分策略可以全局统⼀配置,也可以为单独的表指定拆分策略。

  • 通过hbase-site.xml全局统⼀配置(对hbase所有表⽣效)
1
2
3
4
<property>
<name>hbase.regionserver.region.split.policy</name>
<value>org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy</value>
</property>
  • 通过Java API为单独的表指定Region拆分策略
1
2
3
4
HTableDescriptor tableDesc = new HTableDescriptor("test1");
tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, IncreasingToUpperBoundRegionSplitPolicy.class.getName());
tableDesc.addFamily(new HColumnDescriptor(Bytes.toBytes("cf1")));
admin.createTable(tableDesc);
  • 通过HBase Shell为单个表指定Region拆分策略
1
hbase> create 'test2', {METADATA => {'SPLIT_POLICY' => 'org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy'}},{NAME => 'cf1'}

HBase表的预分区(region)

当⼀个table刚被创建的时候,Hbase默认的分配⼀个region给table。也就是说这个时候,所有的读写请求都会访问到同⼀个regionServer的同⼀个region中,这个时候就达不到负载均衡的效果了,集群中的其他regionServer就可能会处于⽐较空闲的状态。解决这个问题可以⽤pre-splitting,在创建table的时候就配置好,⽣成多个region。

这样的好处:

  • 增加数据读写效率
  • 负载均衡,防⽌数据倾斜
  • ⽅便集群容灾调度region
  • 每⼀个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护

⼿动指定预分区:

1
create 'person','info1','info2',SPLITS => ['1000','2000','3000']

也可以把分区规则创建于⽂件中

1
2
3
4
5
6
vim split.txt
# 文件内容:
aaa
bbb
ccc
ddd

执行:create 'student','info',SPLITS_FILE => '/root/hbase/split.txt'

HBase表Region合并

Region的合并不是为了性能,⽽是出于维护的目的

通过Merge类冷合并Region

  • 需要先关闭hbase集群
  • 需求:需要把student表中的2个region数据进⾏合并:student, ,1593244870695.10c2df60e567e73523a633f20866b4b5和student,1000,1593244870695.0a4c3ff30a98f79ff6c1e4cc927b3d0d

这里通过org.apache.hadoop.hbase.util.Merge类来实现,不需要进⼊hbase shell,直接执⾏(需要先关闭hbase集群):

1
2
3
hbase org.apache.hadoop.hbase.util.Merge student \
student,,1595256696737.fc3eff4765709e66a8524d3c3ab42d59. \
student,aaa,1595256696737.1d53d6c1ce0c1bed269b16b6514131d0.

通过online_merge热合并Region

不需要关闭hbase集群,在线进行合并

1
2
3
4
5
6
7
8
与冷合并不同的是,online_merge的传参是Region的hash值,⽽Region的hash值就是Region名称的最后那段在两个.之间的字符串部分。

需求:需要把student表2个region数据进⾏合并:
student,,1587392159085.9ca8689901008946793b8d5fa5898e06. \
student,aaa,1587392159085.601d5741608cedb677634f8f7257e000.

需要进⼊hbase shell:
merge_region 'c8bc666507d9e45523aebaffa88ffdd6','02a9dfdf6ff42ae9f0524a3d8f4c7777'

HBase API应用和优化

Hbase 客户端API操作

添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
</dependencies>

简单操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package org.javaboy.hbase.client;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

public class HbaseClientDemo {

Configuration conf = null;
Connection conn = null;

@Before
public void init() throws IOException {
// 获取一个配置文件对象
conf = HBaseConfiguration.create();

// 通过conf获取到hbase集群的连接
conf.set("hbase.zookeeper.quorum", "192.168.33.111,192.168.33.112");
conf.set("hbase.zookeeper.property.clientPort", "2181");

// 通过conf获取到hbase集群的连接
conn = ConnectionFactory.createConnection(conf);
}


// 创建一张hbase表
@Test
public void createTable() throws IOException {
// 获取hbaseadmin对象来创建表
HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();

// 创建表描述器
HTableDescriptor student = new HTableDescriptor(TableName.valueOf("student"));

// 指定列族
student.addFamily(new HColumnDescriptor("info"));

// 执行创建的操作
admin.createTable(student);
System.out.println("student表创建成功");
}


// 插入一条数据
@Test
public void putData() throws IOException {
// 需要获取一个table对象
Table student = conn.getTable(TableName.valueOf("student"));

// 准备put对象
Put put = new Put(Bytes.toBytes("110"));// 指定rowkey
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("addr"), Bytes.toBytes("beijing"));

// 插入数据,参数类型是put
student.put(put);

// 关闭table对象
student.close();
System.out.println("插入数据到student表成功");
}

// 删除
@Test
public void deleteData() throws IOException {
Table student = conn.getTable(TableName.valueOf("student"));

// 准备delete对象
Delete delete = new Delete(Bytes.toBytes("110"));

// 执行删除
student.delete(delete);
System.out.println("删除数据成功");
}


// 查询数据
@Test
public void getData() throws IOException {
// 准备table对象
Table student = conn.getTable(TableName.valueOf("student"));

// 准备get对象
Get get = new Get(Bytes.toBytes("110"));
// 指定查询的列族
get.addFamily(Bytes.toBytes("info"));

// 执行查询
Result result = student.get(get);

// 获取到result中所有cell对象
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String f = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));

System.out.println("rowKey = " + rowKey + ", f = " + f + ", column = " + column + ", value = " + value);
}

student.close();
}


// 全表扫描
@Test
public void scanData() throws IOException {
// 准备table对象
Table student = conn.getTable(TableName.valueOf("student"));

// 准备scan对象
Scan scan = new Scan();

// 执行扫描
ResultScanner resultScanner = student.getScanner(scan);

for (Result result : resultScanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String f = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));

System.out.println("rowKey = " + rowKey + ", f = " + f + ", column = " + column + ", value = " + value);
}
}

student.close();
}


// 指定scan开始rowkey和结束rowkey
@Test
public void scanStartEndData() throws IOException {
// 准备table对象
Table student = conn.getTable(TableName.valueOf("student"));

// 准备scan对象
Scan scan = new Scan();

// 指定查询的rowkey区间
scan.setStartRow(Bytes.toBytes("001"));
scan.setStopRow(Bytes.toBytes("2"));

// 执行扫描
ResultScanner resultScanner = student.getScanner(scan);

for (Result result : resultScanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String f = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));

System.out.println("rowKey = " + rowKey + ", f = " + f + ", column = " + column + ", value = " + value);
}
}

student.close();
}

// 释放连接
@After
public void release() {
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

协处理器