细节📕 概述

HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。

NoSQL:非关系型数据库
MySQL:关系型数据库

MySQL:
优点:读写速度比较快
缺点:数据量大之后,读写速度下降
HDFS:
优点:海量存储
缺点:不能修改、删除某条数据,写入读取速度相对比较慢
实时数据处理:
Hbase:优点:海量数据存储、分布式、读取写入速度比较快
redis、mongdb等也是

逻辑上,HBase的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。但从HBase的底层物理存储结构(K-V)来看,HBase更像是一个multi-dimensional map。

啥是多维map
Map[String,Map[String,String]]

HBase数据模型

逻辑结构👇

聊聊HBase-1_其他

HBase表中数据是有顺序的,按照rowkey字典序排序

啥是字典序排序
10 1001 12 21 3
这是从小到大,先比较第一位然后第二位第三位,10和1001的话10后面是空的所以比1001小

物理结构👇

聊聊HBase-1_数据_02

数据模型

1)Name Space(相当于mysql的库

​ 命名空间,类似于关系型数据库的DataBase概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是“hbase”和“default”,“hbase”中存放的是HBase内置的表,“default”表是用户默认使用的命名空间。

2)Region(为了实现数据分布式存储

​ 类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。

3)Row(指逻辑结构的一行 eg:rowkey 1001有多行,但是逻辑结构就1行

​ HBase表中的每行数据都由一个RowKey和多个Column(列)组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。

4)Column

​ HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定,例如info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。

5)Time Stamp

​ 用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入HBase的时间。

6)Cell

​ 由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。

​ cell中的数据是没有类型的,全部是字节码形式存储。

Table: Hbase的数据是以表的形式存在
列簇: 相当于mysql Table的字段,是Hbase的表结构
列限定符: 放在列簇下,是数据的形式存在
Region: Table的一个分段,region保存在不同的服务器上
Store: Region中会根据列簇划分,每个列簇对应一个Store,一个Region中Store的个数=列簇的个数
rowkey: 是每一行数据的唯一标识,相当于mysql的主键。
Hbase中的数据是有顺序的,是按照rowkey的字典序存储
version: 在创建表的时候再列簇上指定的版本号,version代表列簇下的列限定符中最多可以保存多少个历史数据
namespace: 命名空间,相当于Mysql的库
Column: 列, hbase的列 = 列簇:列限定符
Cell: 根据 rowkey+列簇:列限定符+时间戳 确定唯一一个cell
Row: 一行数据,主键相同的为一行数据

HBase基本架构

聊聊HBase-1_其他_03

regionserver职责:
1、存储region
2、负责客户端数据读写请求
3、region split

Store: store的个数 = 列簇的个数
memstore: 是一块内存区域,后续数据会先写入memstore中,等达到一定的条件之后,memstore会flush,flush的时候会生成storeFile
storeFile: memstore flush生成,每 flush一次会生成一个storeFile,最终以HFile这种文件格式保存在HDFS
HLog: 预写日志,因为memstore是内存区域m,所以数据如果直接写到memstore中,可能因为regionserver宕机,造成数据丢失,所以后续写入的时候会将数据写入日志中,等到memstore数据丢失之后,可以从日志中回复数据【一个regionserver只有一个Hlog】

master
1、负责region的故障转移
2、负责region的负载均衡
3、监听regionserver的状态

**架构角色:**👇

1)Region Server

​ Region Server为 Region的管理者,其实现类为HRegionServer,主要作用如下:

​ 对于数据的操作:get, put, delete;

​ 对于Region的操作:splitRegion、compactRegion。

2)Master

​ Master是所有Region Server的管理者,其实现类为HMaster,主要作用如下:

​ 对于表的操作:create, delete, alter

​ 对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。

3)Zookeeper

​ HBase通过Zookeeper来做Master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。

4)HDFS

​ HDFS为HBase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。

HBase快速入门

启动hadoop和zookeeper

在hbase的bin目录下./start-hbase.sh启动hbase

网页端http://hadoop102:16010

提示:如果集群之间的节点时间不同步,会导致regionserver无法启动,抛出ClockOutOfSyncException异常。

修复提示:

a、同步时间服务

请参看帮助文档:Hadoop入门

b、属性:hbase.master.maxclockskew设置更大的值

<property>
        <name>hbase.master.maxclockskew</name>
        <value>180000</value>
        <description>Time difference of regionserver from master</description>
</property>

HBase Shell操作

hbase shell窗口启动
./hbase shell

查看帮助命令👇

help

DDL

Group name: namespace
  Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables

创建所有的命名空间👇

create_namespace ‘bigdata’

查看所有的命名空间👇

list_namespace

删除命名空间👇

drop_namespace ‘bigdata’

注意:命名空间下没有表才可以删除

创建表👇

create ‘student’,‘info’ //在默认’default’命名空间下创建表

create ‘bigdata:student’,‘info’, ‘info2’ //在指定命名空间下创建表

info和info2是列簇

查看所有的表👇

list

查看表的详情👇

describe ‘student’

修改表👇
删除列族信息
alter ‘bigdata:student’,‘delete’=>‘info2’
修改版本信息
alter ‘bigdata:student’,{NAME=>‘info1’,VERSIONS=>3}

删除表👇

disable ‘student’
drop ‘student’

提示:如果直接drop表,会报错:ERROR: Table student is enabled. Disable it first.

DML

Group name: dml
  Commands: append, count, delete, deleteall, get, get_counter, get_splits, incr, put, scan, truncate, truncate_preserve

插入数据到表👇

put ‘student’,‘1001’,‘info:sex’,‘male’

put ‘student’,‘1001’,‘info:age’,‘18’

put ‘student’,‘1002’,‘info:name’,‘Janna’

put ‘student’,‘1002’,‘info:sex’,‘female’

put ‘student’,‘1002’,‘info:age’,‘20’

Scan方式查看数据👇

(全表扫描

扫描全表

scan ‘student’

限定开始位置扫描全表

scan ‘student’,{STARTROW => ‘1001’}

限定开始和结束位置扫描全表

scan ‘student’,{STARTROW => ‘1001’, STOPROW => ‘1001’}

扫描全表数据(包含被标记删除或者应该被覆盖的数据)

scan ‘student’, {RAW => true, VERSIONS => 10}

扫描指定列

scan ‘bigdata:user’,{COLUMNS=>‘base_info:name’}

显示行

scan ‘bigdata:user’,{LIMIT=>1}

Get方式查看数据👇

(必须带上主键查询

指定RowKey查询

get ‘student’,‘1001’

指定RowKey+列族查询

get ‘student’,‘1001’,‘info’

指定RowKey+列族+列名查询

get ‘student’,‘1001’,‘info:name’

指定获取数据版本查询

get ‘student’,‘1001’,{COLUMN=>‘info:name’,VERSIONS=>3}

统计表数据行数👇

count ‘student’

**更新数据(实际上还是Put)**👇

删除数据👇

指定RowKey+列族+列名删除

delete ‘student’,‘1001’,‘info:name’

指定RowKey+列族删除

delete ‘student’,‘1001’,‘info’

指定RowKey删除

deleteall ‘student’,‘1001’

注意:使用的是deleteall命令而不是delete命令

清空表数据👇

truncate ‘student’

HBase进阶

架构原理

聊聊HBase-1_hbase_04
store好处:查询时候减少数据量

1)StoreFile

保存实际数据的物理文件,StoreFile以HFile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的。
HFile中保存了有数据信息、数据索引信息

2)MemStore

写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。

3)WAL(HLog

由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

想看这些东西可以去HDFS里面设置的目录找

假如我创建一个表,它有三个region,每一个region都有startrow和stoprow,这时候我put数据,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7tLRcCa-1622215217363)(…/imgs/Snipaste_2021-02-20_16-17-35.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Vq0n7fZ-1622215217366)(…/imgs/Snipaste_2021-02-20_16-18-04.png)]

scan 'hbase:meta’可以看元数据,上面put的时候怎么找数据?就是通过元数据来的

Hbase元数据: hbase:meta
hbase元数据表中数据的rowkey = 表名,region起始rowkey,时间戳.region名称
hbase元数据中保存的有region的起始rowky,结束rowkey,所处于哪个regionserver
hbase元数据表只有一个region

写流程

聊聊HBase-1_hbase_05
写流程

1)Client先访问Zookeeper,获取hbase:meta表位于哪个RegionServer。

2)访问对应的RegionServer,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。(缓存不一定全缓存完,比如meta有10M大小,缓存只有3M,那么就只会缓存3Mmeta

3)与目标RegionServer进行通讯;

4)将数据顺序写入(追加)到WAL;

5)将数据写入对应的MemStore,数据会在MemStore进行排序;

6)向客户端发送ack;

7)等达到MemStore的刷写时机后,将数据刷写到HFile。

MemStore Flush

聊聊HBase-1_命名空间_06
触发条件👇

1)MemStore级别
当某个MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值128M),会触发MemStore的刷写。(假如一个128M,1个1K,那么都会flush掉,就会产生小文件(所以一般都用一个列簇

2)HRegion级别
当Region中的MemStore的大小达到了
**hbase.hregion.memstore.flush.size(默认值128M)*hbase.hregion.memstore.block.multiplier(默认值4)**时,会阻止继续往该MemStore写数据。

3)HRegionServer级别

当HRegionServer中MemStore的总大小达到
java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)
时,会阻止继续往所有的MemStore写数据。

当RegionServer中MemStore的总大小达到
*java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)
Server会按照其所有MemStore的大小顺序(由大到小)依次进行刷写。

当regionserver上所有的memstore大小小于
java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4) hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)*
会停止flush剩余的region

4)HLog级别
当WAL文件的数量超过hbase.regionserver.maxlogs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.maxlogs以下(该属性名已经废弃,现无需手动设置,最大值为32)。

5)定期刷写
到达自动刷写的时间,也会触发MemStore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时),指的是当前MemStore最后一次编辑时间。

6)手动刷写

用户通过shell命令“flush ‘table’”或者“flush ‘regionname’”分别对一个Region或者多个Region进行flush

读流程

聊聊HBase-1_hbase_07
聊聊HBase-1_缓存_08
布隆过滤器
特点:如果判断存在则不一定存在,如果判断不存在则一定不存在

读流程

1)Client先访问Zookeeper,获取hbase:meta表位于哪个RegionServer。

2)访问对应的RegionServer,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。

3)与目标RegionServer进行通讯;

4)分别在BlockCache(读缓存),MemStore中查询目标数据,如果BlockCache中未查到相应数据则扫描对应的HFile文件,HFile中扫描到的数据块(默认64K)写入BlockCache,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(timestamp)或者不同的类型(Put/Delete)。
假如用setCache缓存了Block Cache100M,查到了2M结果,但是已经有100M了,还是会缓存的,会把老的删掉 然后再次调用会getCache

5)将合并后的最终结果返回给客户端。

StoreFile Compaction

由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。

Compaction分为两种,分别是Minor CompactionMajor Compaction

(1)Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据。

(2)Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且清理掉过期和删除的数据。
聊聊HBase-1_命名空间_09
触发条件👇

Major compact:7天一次
Minor:一次合并要3-10个小文件(符合条件的小文件数必须>=hbase.hstore.compaction.min)。小于128M的称为小文件

Region Split

​ 默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。

**Region Split时机:**👇

1)0.94版本之前:使用的是ConstantSizeRegionSplitPolicy策略。

​ 当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize(默认10g),该Region就会进行拆分。

2)0.94版本-2.0版本:使用的是IncreasingToUpperBoundRegionSplitPolicy。
聊聊HBase-1_数据_10

否则就按照下面的切分方法

​ 当1个Region中的某个Store下所有StoreFile的总大小超过Min(2R^3 * “hbase.hregion.memstore.flush.size”(128M),“hbase.hregion.max.filesize”(10G)),该Region就会进行拆分,其中R为当前Region Server中属于该Table的Region个数。Eg:一个Region Server中有个person表,有两个region里都是person表,那么R就为2
3)2.0版本:使用的是SteppingSplitPolicy。
聊聊HBase-1_数据_11
当前RegionServer中属于该Table的Region个数为1,分裂阈值等于flushSize
2(256M),否则为hbase.hregion.max.filesize(10G)。
# HBase API

在IDEA中创建hbase-demo模块并添加如下依赖

<dependencies>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>2.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>2.0.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
</dependencies>

HbaseDemo.java👀

里面啥都有,还有条件查询啥的

HBase优化

预分区**

​ 每一个region维护着StartRow与EndRow,如果加入的数据符合某个Region维护的RowKey范围,则该数据交给这个Region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。

手动设定预分区👇

create ‘staff1’,‘info’,SPLITS => [‘1000’,‘2000’,‘3000’,‘4000’]

生成16进制序列预分区👇

create ‘staff2’,‘info’,{NUMREGIONS => 5, SPLITALGO => ‘HexStringSplit’}

按照文件中设置的规则预分区👇

创建splits.txt文件内容如下:

aaaa
bbbb
dddd
cccc

create ‘staff3’,‘info’,SPLITS_FILE => ‘/opt/module/splits.txt’

使用JavaAPI创建预分区👇

直接在上面的API创建表中最后一句改改就行

//创建HbaseAdmin实例
HBaseAdmin admin = new HBaseAdmin(HbaseConfiguration.create());
//创建HTableDescriptor实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);

//自定义算法,产生一系列hash散列值存储在二维数组中
byte[][] splitKeys = 某个散列值函数{"10".getBytes(),"20".getBytes(),"30".getBytes()}
//通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表
admin.createTable(tableDesc, splitKeys);

or
    
byte[][] startKey = "10".getBytes();
byte[][] stopKey = "40".getBytes();
admin.createTable(tableDesc,startKey,stopKey,5);

RowKey设计**

**Rowkey设计原则:**👇

1、 长度原则: rowkey的长度不能太长,一般保持在16字节以下

2、 唯一性原则: 数据写入的时候两条数据的rowkey不能相同

3、 Hash原则: 保证数据分散存储

**热点问题解决方案:**👇

一条数据的唯一标识就是RowKey,那么这条数据存储于哪个分区,取决于RowKey处于哪个一个预分区的区间内,设计RowKey的主要目的 ,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。接下来我们就谈一谈RowKey常用的设计方案。

1)rowkey添加随机数
加了之后再查找的时候怎么知道这个id?
可以在生成随机数的时候把原来的rowkey和原来的随机数建立一个映射,保存在类似redis这种地方。后续查的时候根据原来的rowkey加上后面的随机数一组合就能查到

2)字符串反转

20170524000001转成10000042507102
20170524000002转成20000042507102

这样也可以在一定程度上散列逐步put进来的数据。

3)字符串拼接

20170524000001_a12e
20170524000001_93i7

4)取hashCode值作为rowkey

比如:
原本rowKey为1001的,SHA1后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7
原本rowKey为3001的,SHA1后变成:49042c54de64a1e9bf0b33e00245660ef92dc7bd
原本rowKey为5001的,SHA1后变成:7b61dec07e02c188790670af43e717f0f46e8913

在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的rowKey来Hash后作为每个分区的临界值。

内存优化

HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的70%给HBase的Java堆。但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~48G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。

基础优化

1)允许在HDFS的文件中追加内容

hdfs-site.xml、hbase-site.xml

属性:dfs.support.append
解释:开启HDFS追加同步,可以优秀的配合HBase的数据同步和持久化。默认值为true。

2)优化DataNode允许的最大文件打开数

hdfs-site.xml

属性:dfs.datanode.max.transfer.threads
解释:HBase一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为4096或者更高。默认值:4096

3)优化延迟高的数据操作的等待时间

hdfs-site.xml

属性:dfs.image.transfer.timeout
解释:如果对于某一次数据操作来讲,延迟非常高,socket需要等待更长的时间,建议把该值设置为更大的值(默认60000毫秒),以确保socket不会被timeout掉。

4)优化数据的写入效率

mapred-site.xml

属性:
mapreduce.map.output.compress
mapreduce.map.output.compress.codec
解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec或者其他压缩方式。

5)设置RPC监听数量(RPC远程协议类似http)

hbase-site.xml

属性:hbase.regionserver.handler.count
解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。

6)优化HStore文件大小

hbase-site.xml

属性:hbase.hregion.max.filesize
解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。

7)优化HBase客户端缓存

hbase-site.xml

属性:hbase.client.write.buffer
解释:用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。

8)指定scan.next扫描HBase所获取的行数

hbase-site.xml

属性:hbase.client.scanner.caching
解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。

9)flush、compact、split机制

当MemStore达到阈值,将Memstore中的数据Flush进Storefile;compact机制则是把flush出来的小文件合并成大的Storefile文件。split则是当Region达到阈值,会把过大的Region一分为二。涉及属性:

hbase.hregion.memstore.flush.size:134217728

hbase.regionserver.global.memstore.size.lower.limit  0.95 (这个一般要设   0.7? 尽量少flush
hbase.regionserver.global.memstore.size 0.4

0.95参考[MemStore Flush](##MemStore Flush)

整合Phoenix

Phoenix是HBase的开源SQL皮肤。可以使用标准JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据。

Phoenix特点👇

1)容易集成:如Spark,Hive,Pig,Flume和Map Reduce;

2)操作简单:DML命令以及通过DDL命令创建和操作表和版本化增量更改;

3)支持HBase二级索引创建。

Phoenix架构👇
聊聊HBase-1_其他_12

Phoenix Shell操作

启动hadoop、zookeeper和hbase

启动sqlline.py

显示所有表👇

!table 或 !tables

创建表👇

直接指定单个列作为RowKey

CREATE TABLE IF NOT EXISTS person(
id VARCHAR primary key,
name VARCHAR)COLUMN_ENCODED_BYTES = 0;

age年龄一般用int,但是phoenix存数据好像是反码或者补码存的,它只是hbase的皮肤,hbase是原码存,所以还是用varchar好的

在phoenix中,表名和字段名会自动转换为大写,若要小写,使用双引号,如"us_population"。

指定多个列的联合作为RowKey
CREATE TABLE IF NOT EXISTS "us_population" (
"State" CHAR(2) NOT NULL,
"City" VARCHAR NOT NULL,
"Population" BIGINT
CONSTRAINT my_pk PRIMARY KEY (state, city));

查的时候会报错,为什么呢?
查询插入的时候也要带上双引号
select "xx","xx1" from "student"

插入和更新数据👇

upsert into person values(‘1001’,‘zhangsan’);

用hbase shell查看的话发现列限定符是编码形式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nsyiDVzI-1622215495257)(…/imgs/Snipaste_2021-02-23_11-20-21.png)]

建表时侯最后加上

COLUMN_ENCODED_BYTES = 0即可

查询记录👇

select * from student;

select * from student where id=‘1001’;

删除记录👇

delete from person where id=‘1001’;

删除表👇

drop table person;

退出命令行👇

!quit

表的映射

默认情况下,直接在HBase中创建的表,通过Phoenix是查看不到的。如果要在Phoenix中操作直接在HBase中创建的表,则需要在Phoenix中进行表的映射。映射方式有两种:视图映射和表映射。

表映射👇

HBase中不存在表时,可以直接使用create table指令创建需要的表

当HBase中已经存在表时,可以以类似创建视图的方式创建关联表,如下所示

HBase中
create 'PERSON','f1'
put 'PERSON','1002','f1:name','zhangsan'
put 'PERSON','1002','f1:age','40'

Phoenix中 主键可以随意些,其它的要对应上面HBase写的
create table person(
id varchar primary key,
"f1"."name" varchar,
"f1"."age" varchar
)COLUMN_ENCODED_BYTES = 0;

视图映射👇

HBase中
create 'PERSON','f1'
put 'PERSON','1002','f1:name','zhangsan'
put 'PERSON','1002','f1:age','40'

Phoenix中 主键可以随意些,其它的要对应上面HBase写的
create view person(
id varchar primary key,
"f1"."name" varchar,
"f1"."age" varchar
)COLUMN_ENCODED_BYTES = 0;

upsert into person values('1001','lisi','50');发现报错Table is read only

这两个有什么区别呢?

视图只能查询,其它操作都不可以

drop view person;后hbase表中的表不会删除

Hbase表本身不存在就不能创建视图表

命名空间表映射👇

创建表时候会创建在默认命名空间下

如果需要映射则修改hbase的hbase-site.xml与phoenix中的hbase-site.xml

修改/opt/module/hbase/conf/hbase-site.xml与/opt/module/phoenix/bin/hbase-site.xml的配置文件
<property>
		<name>phoenix.schema.isNamespaceMappingEnabled</name>
	    <value>true</value>
</property>
<property>
		<name>phoenix.schema.mapSystemTablesToNamespace</name>
	    <value>true</value>
</property>

分发/opt/module/hbase/conf/hbase-site.xml

创建schema
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create schema “bigdata2”
注意: schema的名称必须与命名空间的名称一致

使用schema
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> use “bigdata2”

建立映射

create table "student"(
id varchar primary key,
"f1"."name" varchar,
"f1"."age" varchar,
"f1"."address" varchar)COLUMN_ENCODED_BYTES=0

 or  create table "bigdata2"."student"......

检查结果,下面搜索出来的是hbase里面本来表有的,age没有显示,因为之前插入的Int类型显示会有问题,所以才说用varchar比较好

聊聊HBase-1_其他_13

Phoenix JDBC操作

启动queryServer服务

[atguigu@hadoop102 phoenix]$ bin/queryserver.py 这个能看监听的端口号是啥

或者queryserver.py start 这个算是后台挂起

创建项目并导入依赖

<dependencies>
<dependency>
       <groupId>org.apache.phoenix</groupId>
       <artifactId>phoenix-queryserver-client</artifactId>
       <version>5.0.0-HBase-2.0</version>
    </dependency>
    <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
       <version>4.5.6</version>
       <scope>provided</scope>
   </dependency>
   <dependency>
       <groupId>com.google.protobuf</groupId>
       <artifactId>protobuf-java</artifactId>
       <version>3.5.1</version>
   </dependency>
   <dependency>
       <groupId>io.grpc</groupId>
       <artifactId>grpc-netty</artifactId>
       <version>1.10.0</version>
   </dependency>
<dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
    </dependency>
</dependencies>

突然想到看看虚拟机内存占用了多少,参考实时监控进程

PhoenixTest.java👀啥都有

Phoenix二级索引**

get快不快,快呀,因为👇
get -> rowkey -> meta -> region -> regionserver
get (找到) rowkey (从元数据表中查找rowkey) meta (从元数据表中查找region) region (同上) regionserver

那么如何让scan快点?
两种方案
假如再表中可能经常用[name age address]字段查询
那就根据这三个字段建三个表
第一个表对应的rowkey以name对应的value值和原来的rowkey作为新的rowkey
全局索引:name value_rowkey

然后就是本地索引,参考下面

二级索引配置文件

添加如下配置到HBase的HRegionserver节点的hbase-site.xml

<!-- phoenix regionserver 配置参数-->
	<property>
		<name>hbase.regionserver.wal.codec</name>
		<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
	</property>

	<property>
		<name>hbase.region.server.rpc.scheduler.factory.class</name>
		<value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
		<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
	</property>

	<property>
		<name>hbase.rpc.controllerfactory.class</name>
		<value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
		<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
	</property>

xsync /opt/module/hbase/conf/hbase-site.xml

再重新启动一下hbase

全局二级索引

Global Index是默认的索引格式,创建全局索引时,会在HBase中建立一张新表。也就是说索引数据和数据表是存放在不同的表中的,因此全局索引适用于多读少写的业务场景。

写数据的时候会消耗大量开销,因为索引表也要更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。

在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。

查看执行计划👇
explain select * from “student” where “name”=‘zhangsan-0’;
聊聊HBase-1_数据_14
创建单个字段的全局索引👇
create index student_index on “bigdata2”.“student”(“f1”.“name”);
聊聊HBase-1_hbase_15
聊聊HBase-1_其他_16
执行查询(注意和上面的查询不一样
explain select “name” from “student” where “name”=‘zhangsan-0’;
聊聊HBase-1_数据_17
但是
explain select * from “student” where “name”=‘zhangsan-0’;
聊聊HBase-1_数据_18
结果怎么还是full scan?
scan查看一下
聊聊HBase-1_缓存_19
发现数据里面没有保存什么有用的信息,只有name+rowkey组合
select * 的话是包含了所有字段,还有AGE字段,所以索引表找不到,就变成了全表查询
发现这种查询并不太好

删掉它
drop index STUDENT_INDEX on “bigdata2”.“student”;

创建携带其他字段的全局索引👇
create index student_index on “student”(“f1”.“name”) include(“f1”.“age”,“f1”.“address”);

再次扫描发现不一样了

聊聊HBase-1_hbase_20
聊聊HBase-1_命名空间_21
查询也不一样了
聊聊HBase-1_其他_22
用所有字段来创建索引👇

create index student_index2 on “student”(“f1”.“name”,“f1”,“age”,“f1”.“address”);

发现单独查age是full scan 为什么?

聊聊HBase-1_其他_23
会先根据name比较大小,假如来了个50,50比a小,但是不在里面。

那么为什么光查name可以? 因为 假如查zhangsan z肯定比w大所以落在第三个region上面

如果用where name or age来查也会是full scan,因为or肯定还会查age,找不到它,所以是这样子

所以这个全局二级索引也不太好

本地二级索引(ES工作中用)**

工作中估计不用phoenix建索引,而是用ES
ES -> 写入JAVA
聊聊HBase-1_数据_24
Local Index适用于写操作频繁的场景。

索引数据和数据表的数据是存放在同一张表中(且是同一个Region),避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。查询的字段不是索引字段索引表也会被使用,这会带来查询速度的提升。

本地二级索引不建新表

create local index STUDENT_LOCAL_INDEX on “student”(“f1”.“name”,“f1”.“age”);
聊聊HBase-1_数据_25
聊聊HBase-1_数据_26
它是以两个下划线开头又因为按字典序排序,所以所有的rowkey都集中在一起这样就不用查找所有的region了。
所以能单独where age=?

eg:看上面图片,1001前面两个,lisi前面两个就是下划线,它们聚在一起,查找时候直接找下划线然后一个一个搜就行。不用管startrowkey这些,也就是不用管1001、1002啥的。跟全局二级索引的不一样!

所以工作时候都用本地二级索引

因为本地二级索引要用两个下划线开头,所以自己设计rowkey不能这样搞。

HBase协处理器(扩展)

二级索引底层怎么实现的?

就是这个HBase协处理器

类似于监听器
假如你做了什么动作eg:put -> 。。。
如果执行了put操作,就会自动触发。。。操作

那么插入数据时候想让它自己生成索引

这就是协处理器做的事情

案例需求👇

编写协处理器,实现在往A表插入数据的同时让HBase自身(协处理器)向B表中插入一条数据。

实现步骤

创建一个maven项目,并引入以下依赖。

<dependencies>
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-client</artifactId>
        <version>2.0.5</version>
        <scope>provided</scope>
</dependency>

    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-server</artifactId>
        <version>2.0.5</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

定义FruitTableCoprocessor类并继承BaseRegionObserver类

package com.atguigu;

import org.apache.hadoop.hbase.HBaseConfiguration;
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 java.io.IOException;

public class FruitTableCoprocessor implements RegionObserver, RegionCoprocessor {

    @Override
    public Optional<RegionObserver> getRegionObserver() {
        return Optional.of(this);
    }

    @Override
    public void postPut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability) throws IOException {
        //获取连接
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum","hadoop102:2181,hadoop103:2181,hadoop104:2181");
        Connection connection = ConnectionFactory.createConnection(conf);
        //获取表对象
        Table table = connection.getTable(TableName.valueOf("fruit"));
        //插入数据
        table.put(put);
        //关闭资源
        table.close();
        connection.close();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-27I3FR3w-1622215758488)(…/imgs/Snipaste_2021-02-23_18-44-37.png)]

打包放入HBase的lib目录下

分发jar包并重启HBase

建表时指定注册协处理器

tableDescriptorBuilder.setCoprocessor(“com.atguigu.FruitTableCoprocessor”);

注意: 还可以通过不重启的方式动态加载协处理器

1) 给hbase-site.xml中添加配置,防止协处理器异常导致集群停机

<!-- 如果协处理器报错会跳过 -->
<property>
	<name>hbase.coprocessor.abortonerror</name>
	<value>false</value>
</property>

2) 打成jar包上传hdfs,比如存放路径为 hdfs://hadoop102:9820/c1.jar

3) 禁用表: disable ‘A’

4) 加载协处理器:

alter ‘表名’, METHOD => ‘table_att’, ‘Coprocessor’=>‘jar所处HDFS路径| 协处理器所在的全类名|优先级|参数’

alter ‘t1’,‘coprocessor’=>‘hdfs://hadoop102:8020/c1.jar|全类名|1|(arg1=xx)不用写括号里的,因为不传参数,但是竖线别删’

5) 启动表: enable ‘A’

  1. 向A表插入数据,观察B表是否同步有数据插入
与Hive的集成

HBase与Hive的对比

1.Hive👇

(1) 数据仓库

Hive的本质其实就相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系,以方便使用HQL去管理查询。

(2) 用于数据分析、清洗

Hive适用于离线的数据分析和清洗,延迟较高。

(3) 基于HDFS、MapReduce

Hive存储的数据依旧在DataNode上,编写的HQL语句终将是转换为MapReduce代码执行。

2.HBase👇

(1) 数据库

是一种面向列族存储的非关系型数据库。

(2) 用于存储结构化和非结构化的数据

适用于单表非关系型数据的存储,不适合做关联查询,类似JOIN等操作。

(3) 基于HDFS

数据持久化存储的体现形式是HFile,存放于DataNode中,被ResionServer以region的形式进行管理。

(4) 延迟较低,接入在线业务使用

面对大量的企业数据,HBase可以直线单表大量数据的存储,同时提供了高效的数据访问速度。

HBase与Hive集成使用

在hive-site.xml中修改zookeeper的属性,如下:

	<property>
        <name>hive.zookeeper.quorum</name>
        <value>hadoop102,hadoop103,hadoop104</value>
    </property>

    <property>
        <name>hive.zookeeper.client.port</name>
        <value>2181</value>
    </property>

1、内部表👇
数据固定存在/user/hive/warehouse/库名/表名/
在hive创建表的时候会同步在hbase中创建表,如果hbase的表已经存在则报错
删除hive内部表的时候会同步删除Hbase的表

CREATE TABLE student_hive_2(id string,name string,age string) 
		STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
		WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,f1:name,f1:age")  //就是对应着上面的id,name,age一一映射
		TBLPROPERTIES ("hbase.table.name" = "student_hbase");

​ hbase.columns.mapping: 指定hive的字段与hbase的column的映射
​ :key 指hbase的主键
​ hbase.table.name: 指定创建的hbase的表名

**2、外部表[与hbase的表建立映射关系]**👇
数据可以在HDFS任何地方
在hive创建表的时候要求hbase的表必须已经存在,如果hbase的表不存在则报错
删除hive外部表的时候不会删除hbase的表

CREATE EXTERNAL TABLE student_hive_2(id string,name string,age string) 
		STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
		WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,f1:name,f1:age") //就是对应着上面的id,name,age一一映射
		TBLPROPERTIES ("hbase.table.name" = "student_hbase");

​ hbase.columns.mapping: 指定hive的字段与hbase的column的映射
​ :key 指hbase的主键
​ hbase.table.name: 指定hbase的表名

案例一👇

目标:建立Hive表,关联HBase表,插入数据到Hive表的同时能够影响HBase表。

分步实现:

1)在Hive中创建表同时关联HBase

CREATE TABLE hive_hbase_emp_table(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");

提示:完成之后,可以分别进入Hive和HBase查看,都生成了对应的表

2)在Hive中创建临时中间表,用于load文件中的数据

提示:不能将数据直接load进Hive所关联HBase的那张表中

CREATE TABLE emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
row format delimited fields terminated by '\t';

3)向Hive中间表中load数据

hive> load data local inpath '/home/admin/softwares/data/emp.txt' into table emp;

4)通过insert命令将中间表中的数据导入到Hive关联Hbase的那张表中

hive> insert into table hive_hbase_emp_table select * from emp;

5)查看Hive以及关联的HBase表中是否已经成功的同步插入了数据

Hive:

hive> select * from hive_hbase_emp_table;

HBase:

hbase> scan 'hbase_emp_table'

案例二👇

目标:在HBase中已经存储了某一张表hbase_emp_table,然后在Hive中创建一个外部表来关联HBase中的hbase_emp_table这张表,使之可以借助Hive来分析HBase这张表中的数据。

注:该案例2紧跟案例1的脚步,所以完成此案例前,请先完成案例1。

分步实现:

1)在Hive中创建外部表

CREATE EXTERNAL TABLE relevance_hbase_emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY 
'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = 
":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno") 
TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");

2)关联后就可以使用Hive函数进行一些分析操作了

hive (default)> select * from relevance_hbase_emp;
杂项🚀 安装配置

tar -zxvf hbase-2.0.5-bin.tar.gz -C /opt/module

mv /opt/module/hbase-2.0.5 /opt/module/hbase

cd conf/

vim hbase-env.sh
修改内容如下
export HBASE_MANAGES_ZK=false

vim regionservers
修改内容如下
hadoop102
hadoop103
hadoop104

vim hbase-site.xml
修改如下内容

<property>
	<name>hbase.rootdir</name>
	<value>hdfs://hadoop102:8020/hbase</value>
</property>

<property>
    <name>hbase.unsafe.stream.capability.enforce</name>
     <value>false</value>
</property>

<property>
	<name>hbase.cluster.distributed</name>
	<value>true</value>
</property>

<property>
	<name>hbase.zookeeper.quorum</name>
	<value>hadoop102,hadoop103,hadoop104</value>
</property>

[atguigu@hadoop102 module]$ xsync hbase/

Phoenix安装

1)官网地址

http://phoenix.apache.org/

2)Phoenix部署

(1)上传并解压tar包

[atguigu@hadoop102 module]$ tar -zxvf /opt/software/apache-phoenix-5.0.0-HBase-2.0-bin.tar.gz -C /opt/module

[atguigu@hadoop102 module]$ mv apache-phoenix-5.0.0-HBase-2.0-bin phoenix

(2)配置环境变量

sudo vim /etc/profile.d/my_env.sh 

#phoenix
export PHOENIX_HOME=/opt/module/phoenix
export PHOENIX_CLASSPATH=$PHOENIX_HOME
export PATH=$PATH:$PHOENIX_HOME/bin

source /etc/profile

(3)复制server包并拷贝到各个节点的hbase/lib

[atguigu@hadoop102 module]$ cd /opt/module/phoenix/

[atguigu@hadoop102 phoenix]$ cp phoenix-5.0.0-HBase-2.0-server.jar /opt/module/hbase/lib/
[atguigu@hadoop102 phoenix]$ scp phoenix-5.0.0-HBase-2.0-server.jar hadoop103:/opt/module/hbase/lib/
[atguigu@hadoop102 phoenix]$ scp phoenix-5.0.0-HBase-2.0-server.jar hadoop104:/opt/module/hbase/lib/

(4)重启HBase

ps -ef | grep hbase | grep -v grep | awk ‘{print $2}’ | xargs kill -9 失败的话用这个可以杀死进程

(5)连接Phoenix

[atguigu@hadoop102 phoenix]$ bin/sqlline.py (hadoop102,hadoop103,hadoop104:2181)括号里面的老师没有加