HBase快速入门

HBase快速入门

一、基本概念

Apache HBase(Hadoop DataBase)是一个开源的、高可靠性、高性能、面向列(这里指列族,非列式存储)、可伸缩、实时读写的分 布式数据库,其设计思想来源于 Google 的 BigTable 论文。利用 Hadoop HDFS 作为其文件存储系统,利用 ZooKeeper 作为其分布式协同服务。

注意:HBase 是列族数据库(Column-Family Database),不是列式数据库(Column-Oriented Database)。

特点:易扩展、容量大、面向列、多版本、稀疏性、高可靠。

HBase 是一种 NoSQL 数据库,这意味着它不像传统的 RDBMS 数据库那样支持 SQL 作为查询语言。

总结:HBase 是运行在 HDFS 之上的面向列(列族)的数据库管理系统。

image-20231120103224455

  • HBase与RDBMS区别:

image-20231120103505612

二、数据模型

在HBase表中,一条数据拥有一个全局唯一的主键(Row key)和任意数量的列,每个列中的数据支持多个版本,一列或多列组成一个列族,同一个列族中列的数据存储在同一个Hfile中。这样基于列存储的数据结构有利于数据缓存和查询。

HBase中数据定位需要通过:RowKey->Column Family->Column Qualifier->version

image-20231120103937937

  • NameSpace

命名空间类似于关系型数据库的概念,本质是表的逻辑分组。命名空间可以创建和删除。HBase有两个特殊的预定义命名空间:

default:没有明确指定命名空间的表将自动落入此命名空间

hbase:系统命名空间,用于包含 HBase 的内部表和元数据表

  • Table

行和列组成

  • RowKey

RowKey 的概念与关系型数据库中的主键相似,是一行数据的唯一标识。RowKey 可以是任意字符串(最大长度是64KB,实际应用中长度 一般为 10-100 Bytes),RowKey 以字节数组保存。

  • Column Family

Column Family 即列族,HBase 基于列划分数据的物理存储,同一个列族中列的数据在物理上都存储在同一个 HFile 中。一个列族可以包 含任意多列,一般同一类的列会放在一个列族中,每个列族都有一组存储属性,HBase 在创建表的时候就必须指定列族。HBase 的列族不是越多越好,官方推荐一个表的列族数量最好小于或者等于三,过多的列族不 利于 HBase 数据的管理和索引。

  • . Column Qualifier

 列族的限定词,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识。使用的时候必须 列族:列 ,列可以 根据需求动态添加或者删除,同一个表中不同行的数据列都可以不同。

  • Cell

Cell 由 Row,Column Family,Column Qualifier,Version 组成。Cell 中的数据是没有类型的,全部使用字节码形式存贮,因为 HDFS 上的 数据都是字节数组。

image-20231120105526371

三、架构模型

HBase 一般运行在 HDFS 上,以 HDFS 作为基础 的存储设施。用户通过 HBase Client 提供的 Shell 或 Java API 来访问 HBase 数据库,以完成数据的写入和读取。HBase 集群主要由 HMaster、 HRegionServer 和 ZooKeeper 组成

image-20231120105659593

  • Zookeeper

选举HMaster、监控HRegionServer(节点探活)、维护元数据和集群配置

image-20231120105919986

  • client

HBase Client 为用户提供了访问 HBase 的接口,可以通过元数据表(客户端负责发送请求到数据库)来定位到目标数据的 HRegionServer。

DDL:数据库定义语言(表的建立,删除,添加删除列族,控制版本)

DML:数据库操作语言(增删改)

DQL:数据库查询语言(查询,全表扫描,基于主键,基于过滤器)

image-20231120112234612

  • HMaster

HMaster 是 HBase 集群的主节点,负责整个集群的管理工作,HMaster可以实现高可用(Active 和 Backup),通过 ZooKeeper 来维护主备节点的切换。

管理分配:管理和分配 HRegion,负责启动的时候分配 HRegion 到具体的 HRegionServer,又或者在分割 HRegion 时关于新 HRegion 的 分配。管理用户对 Table 结构的 DDL(创建,删除,修改)操作。

负载均衡:一方面负责将用户的数据均衡地分布在各个 HRegionServer 上,防止 HRegionServer 数据倾斜过载。另一方面负责将用户的 请求均衡地分布在各个 HRegionServer 上,防止 HRegionServer 请求过热;

维护数据:发现失效的 HRegion,并将失效的 HRegion 分配到正常的 HRegionServer 上。当某个 HRegionServer 下线时迁移其内部的 HRegion 到其他 HRegionServer上。

权限控制。

  • HRegionServer

HRegionServer 直接对接用户的读写请求,是真正干活的节点,属于 HBase 具体数据的管理者。

  • HRegion

一个 HRegionServer 包含了多个 HRegion。HBase 将表中的数据基于 RowKey 的不同范围划分到不同 HRegion 上,每个 HRegion 都负责一 定范围的数据存储和访问。

image-20231120113306011

  • HFile

Block:每个 HFile 由 N 个 Block 组成。

KeyValue:每个 Block 又是由多个 KeyValue 数据组成,KeyValue 对象是数据存储的核心,KeyValue 包装了一个字节数组,同时将偏移量 offsets 和 lengths 放入数组中,这个数组指定从哪里开始解析数据内容。

KeyValue 对象不跨 Block 存储,假如这里有一个 KeyValue 的大小为 8M,即使 Block-Size=64KB,当读取该 KeyValue 的时候也是以一个 连贯的 Block 进行读取。

读取一个 HFile 时,会首先读取 Trailer,Trailer 有指针指向其他数据块的起始点,保存了每个 段的起始位置(段的 Magic Number 用来做安全 Check),然后 Data Block Index 会被读取到内存中,这样,当检索某个 Key 时,不需要扫 13 No. 13 / 75 描整个 HFile,而只需从内存中找到 Key 所在的 Block,通过一次磁盘 IO 将整个 Block 读取到内存中,再找到需要的 Key。

image-20231120113649046

image-20231120113721958

  • HLog

一个 HRegionServer 只有一个 HLog 文件。负责记录数据的操作日志,当 HBase 出现故障时可以进行日志重放、故障恢复。例如磁盘掉 电导致 MemStore 中的数据没有持久化存储到 StoreFile,这时就可以通过 HLog 日志重放来恢复数据。

Timestamp:写入时间。

Sequence Number:起始值为 0,或者是最近一次存入文件系统中的 Sequence Number。

image-20231120114132289

  • HDFS

HDFS为HBase提供了底层数据存储,同时为HBase提供了高可用的支持,HLog存储在HDFS上,服务器发生宕机时,可以重放日志HLog来恢复数据。

MYSQL数据直接落盘,HBase数据落入HDFS中,HDFS数据落入磁盘中。

四、集群搭建

前提:Hadoop 正常运行,ZooKeeper 正常运行,Hive 正常运行,服务器时间同步。

image-20231120114527835

  • 安装

将准备好的安装包上传到Node1,然后解压

 tar -zxvf hbase-2.5.3-bin.tar.gz -C /usr/local/hbase/
 rm hbase-2.5.3-bin.tar.gz -rf
  • 修改配置文件

修改环境配置文件 hbase-env.sh

cd /usr/local/hbase/hbase-2.5.3/conf/
vim hbase-env.sh

添加以下内容

export JAVA_HOME=/usr/java/jdk1.8.0_351-amd64
export HADOOP_HOME=/opt/yjx/hadoop-3.3.4/
export HBASE_MANAGES_ZK=false
export HBASE_LOG_DIR=${HBASE_HOME}/logs
# HBase的Jar包和Hadoop的Jar包会有冲突,会导致服务启动失败。设置以下参数禁止启动时扫描Hadoop的Jar包
export HBASE_DISABLE_HADOOP_CLASSPATH_LOOKUP="true"

修改文件hbase-site.xml

vim hbase-site.xml

删除configuration节点中的所有内容,然后再在configuration节点中添加以下内容

# 由于configuration标签内的配置信息过多,建议在vim末行命令下执行以下命令:
:set num  #显示所有行号   
:12,13d  #删除12行到13行的数据 
<!-- 设置 HBase 数据存储的位置(存储在 HDFS 上的位置) -->
<!-- 使用本地文件系统例如:file:///root/hbase/data -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://hdfs-yjx/hbase</value>
</property>
<!-- 是否为分布式模式部署,true 表示分布式部署 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 设置 HBase 的 ZK 集群地址,以逗号分隔 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>node01,node02,node03:2181</value>
</property>
<!-- 设置 HBase 在 ZK 上的数据根目录 znode 节点名称 -->
<property>
<name>zookeeper.znode.parent</name>
<value>/hbase</value>
</property>
<!-- 本地文件系统的临时目录,默认在 /tmp,/tmp 会在服务器重启时被清除,一般配置成本地文件模式时才需要设置 -->
<property>
<name>hbase.tmp.dir</name>
<value>/var/yjx/hbase</value>
</property>
<!-- 控制 HBase 是否检查流功能(hflush/hsync),如果要在 HDFS 系统上运行,请禁用此选项 -->
<!-- 简单的理解就是:使用 HDFS 存储将其设置为 false,使用本地文件系统将其设置为 true -->
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>

修改regionserver

 vim regionservers

regionserver内容

node1
master
node2

添加备用主机backup-masters

vim backup-masters

backup-masters内容

node02

把hadoop中的core-site.xml和hdfs-site.xml拷贝到hbase中的conf目录下

cp /usr/local/hadoop/hadoop-3.3.4/etc/hadoop/core-site.xml /usr/local/hbase/hbase-2.5.3/conf/

cp /usr/local/hadoop/hadoop-3.3.4/etc/hadoop/hdfs-site.xml /usr/local/hbase/hbase-2.5.3/conf/

最后把配置好的HBase配置文件分发到其他节点(三个都要配置)

修改环境配置(三个节点都要修改环境变量),修改完成后 source /etc/profile 重新加载环境变量

export HBASE_HOME=/opt/yjx/hbase-2.5.3
export PATH=$HBASE_HOME/bin:$PATH
  • 启动

启动ZooKeeper(三台机器都需要执行)

zkServer.sh start
zkServer.sh status

启动HDFS

start-dfs.sh

启动HBase

start-hbase.sh

查看各个进程

jps

image-20231120120033355

 Web 访问:http://node01:16010 查看HRegion服务。

image-20231120120131261

五、HBase Shell

  • 基本命令(先用 HBase 提供的命令行工具进行交互,位于 HBase 的 /bin 目录下)
hbase shell
  • 查看信息
status 'simple' # 查看简单信息
status 'summary' # 查看概要信息
status 'replication' # 查看副本信息
status #查看服务器状态。
version #查看版本
processlist #查看当前运行的任务列表。
whoami  #当前登录用户是谁
  • NameSpace命令

    image-20231120120918110

# 查看所有命令空间
hbase> list_namespace
# 使用正则查看表
hbase> list_namespace 'h.*'
# 指定查询
hbase> list_namespace 'hbase'
# 查看指定命令空间下的表
hbase> list_namespace_tables 'hbase'
# 创建 ns1 命名空间
hbase> create_namespace 'ns1'
# 创建 ns1 命名空间并添加属性
hbase> create_namespace 'ns1', {'PROPERTY_NAME'=>'PROPERTY_VALUE'}
# 修改命名空间,添加属性
hbase> alter_namespace 'ns1', {METHOD => 'set', 'PROPERTY_NAME' => 'PROPERTY_VALUE'}
# 修改命名空间,移除属性
hbase> alter_namespace 'ns1', {METHOD => 'unset', NAME=>'PROPERTY_NAME'}
# 删除命名空间,命名空间必须为空
hbase> drop_namespace 'ns1'
  • DDL命令

image-20231120120957415

# 在命名空间 ns1 下创建表名为 t1 的表,列族 f1 版本数为 5
hbase> create 'ns1:t1', {NAME => 'f1', VERSIONS => 5}
# 在命名空间 default 下创建表名为 t2 的表,列族 f1、列族 f2、列族 f3 版本数均为 1
hbase> create 't2', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
# 上条命令简写方式如下
hbase> create 't3', 'f1', 'f2', 'f3'
# 是否使用 BlockCache
hbase> create 't4', {NAME => 'f1', VERSIONS => 2, BLOCKCACHE => true}
hbase> list
TABLE
t2
t3
t4
ns1:t1
4 row(s)
Took 0.0105 seconds
=> ["t2", "t3", "t4", "ns1:t1"]
hbase> describe 't2'
hbase> describe 'ns1:t1'
hbase> describe 't2'
hbase> describe 'ns1:t1'
hbase> exists 't1'
#查看表在 HDFS 上的数据。
hdfs dfs -ls /hbase/data/default/
hdfs dfs -ls /hbase/data/ns1/
# 修改 ns1:t1 表的 f1 列族版本数为 3
hbase> alter 'ns1:t1', {NAME => 'f1', VERSIONS => 3}
# 删除 t3 表的 f2 列族
hbase> alter 't3', {NAME => 'f2', METHOD => 'delete'}
# 给 t3 表添加 f4 列族,版本数为 5
hbase> alter 't3', {NAME => 'f4', VERSIONS => 5}
# 表必须处于禁用状态才可以被删除
hbase> disable 't3'
hbase> drop 't3'
# 使用正则删除多张表
hbase> drop_all 't.*'
hbase> drop_all 'ns:t.*'
hbase> drop_all 'ns:.*'
# 自定义切分点
# create 'tableName', 'columnFamily', {SPLITS => ['rowKey1', 'rowKey2', 'rowKey3']}
create 't5', 'f1', {SPLITS => ['10', '20', '30', '40', '50']}
# 基于随机字节键创建具有 8 个区域的表
hbase> create 't6', 'f1', {NUMREGIONS => 8, SPLITALGO => 'UniformSplit'}
# 基于十六进制创建具有 10 个区域的表
hbase> create 't7', 'f1', {NUMREGIONS => 10, SPLITALGO => 'HexStringSplit'}
  • DML

image-20231120121423190

# 创建 t_user 表,列族 personal 和列族 office 版本数均为 3
hbase> create 't_user', {NAME => 'personal', VERSIONS => 3}, {NAME => 'office', VERSIONS => 3}, {SPLITS => ['user-1',
'user-2', 'user-4']}
   put :插入记录。
注意:现在表的数据都在内存中,并没有落地到磁盘。如果这时想要落地到磁盘只能手动落地。
命令: flush 'tableName' ,例如: flush 't_user' 。然后在 HDFS 中就可以查看到对应的数据了。
   count :统计表的行数。
   get :获取记录。
   get_splits :获取表分割信息。
   incr :让某个单元格的数据自增,可以指定自增数。
  给 user-1 增加 personal:age 字段,并使用 Counter 实现递增。
   get_counter :获取当前计数器的值。
   append :追加记录。
# put 'tableName', 'rowKey', '列', '值'
# 通过 Shell 插入的 int 类型值会被自动转为 String
put 't_user', 'user-1', 'personal:name', 'zhangsan'
put 't_user', 'user-1', 'personal:gender', 1
put 't_user', 'user-1', 'office:phone', '13800000000'
put 't_user', 'user-1', 'office:address', 'beijing'
put 't_user', 'user-1', 'office:phone', '13000000000'
put 't_user', 'user-1', 'office:address', 'tianjin'
put 't_user', 'user-1', 'office:phone', '13800000000'
put 't_user', 'user-1', 'office:address', 'shanghai'
#count :统计表的行数
hbase> count 't_user'
# 获取一个 RowKey 的所有数据
hbase> get 't_user', 'user-1'
# 获取一个 RowKey 的某个列族的所有数据
hbase> get 't_user', 'user-1', 'personal'
# 获取一个 RowKey 的某个列族中某个列的所有数据
hbase> get 't_user', 'user-11', 'personal:age'
#get_splits :获取表分割信息。
hbase> get_splits 't_user'
Total number of splits = 4
user-1
user-2
user-4
Took 0.1432 seconds
=> ["user-1", "user-2", "user-4"]
#追加记录
hbase> put 't_user', 'user-33', 'personal:gender', 0
hbase> append 't_user', 'user-33', 'office:phone', '18100000000'
hbase> append 't_user', 'user-33', 'office:address', 'sichuan'
hbase> get 't_user', 'user-33'
# 全表扫描
hbase> scan 't_user'
# 扫描时指定列族
hbase> scan 't_user', {COLUMNS => 'personal'}
#删除数据
hbase> delete 't_user', 'user-33', 'office:phone'
hbase> get 't_user', 'user-33'
#  truncate :清空表,会把表分区也清除掉。 truncate_preserve :清空表,只清除数据。
hbase> truncate 'namespace:tableName'
hbase> truncate_preserve 'namespace:tableName'

注意:现在表的数据都在内存中,并没有落地到磁盘。如果这时想要落地到磁盘只能手动落地。 命令: flush 'tableName' ,例如: flush 't_user' 。然后在 HDFS 中就可以查看到对应的数据了。

  • DQL
# 获取一个 RowKey 的所有数据
hbase> get 't_user', 'user-1'
# 获取一个 RowKey 的某个列族的所有数据
hbase> get 't_user', 'user-1', 'personal'
# 获取一个 RowKey 的某个列族中某个列的所有数据
hbase> get 't_user', 'user-11', 'personal:age'
# 获取一个 RowKey 的某个列族中某个列的多版本数据
hbase> get 't_user', 'user-1', {COLUMN => 'office:phone', VERSIONS => 3}
# 通过 Timestamp 获取指定版本的数据
hbase> get 't_user', 'user-1', {COLUMN => 'office:phone', TIMESTAMP =>1661776792305}
# 全表扫描
hbase> scan 't_user'
# 扫描时指定列族
hbase> scan 't_user', {COLUMNS => 'personal'}
# 扫描时指定列族,并显示最新的 3 个版本的内容
hbase> scan 't_user', {COLUMNS => 'office', VERSIONS => 3}
# 扫描时指定列族和列名,并显示最新的 3 个版本的内容
hbase> scan 't_user', {COLUMNS => 'office:phone', VERSIONS => 3}
# 设置开启 Raw 模式,开启 Raw 模式会把那些已添加删除标记但是未实际删除的数据也显示出来
hbase> scan 't_user', {COLUMNS => 'personal', RAW => true}
# 查询 t_user 表中列族为 personal 和 office 的数据
hbase> scan 't_user', {COLUMNS => ['personal', 'office']}
hbase> scan 't_user', {COLUMNS => ['personal', 'office'], RAW => true}
# 查询 t_user 表中列族为 personal,列名为 name 和列族为 office,列名为 phone 的数据
hbase> scan 't_user', {COLUMNS => ['personal:name', 'office:phone']}
# 查询 t_user 表中列族为 personal 和 office 且列名含有 a 字符的数据
hbase> scan 't_user', {COLUMNS => ['personal', 'office'], FILTER => "(QualifierFilter(=, 'substring:a'))"}
# 查询 t_user 表中列族为 personal,RowKey 范围是 [user-1, user-2) 的数据
hbase> scan 't_user', {COLUMNS => 'personal', STARTROW => 'user-1', ENDROW => 'user-2'}
# 查询 t_user 表中 RowKey 以 user-1 字符开头的
hbase> scan 't_user', {FILTER=>"PrefixFilter('u')"}
hbase> scan 't_user', {FILTER=>"PrefixFilter('user-1')"}
# 查询 t_user 表中指定时间范围的数据
hbase> scan 't_user', {TIMERANGE => [1661777937728, 1661777975593]}
# zk_dump :查看 ZK 中的 HBase 节点信息。
hbase> zk_dump

六、读写流程

client-->zookeeper-->HBase meta-->表的region(分区rowKey) (三层索引直接定位到表的区域)

image-20231120135923605

image-20231120135944082

  • Hbase读取流程(为什么 HBase 可以做到百亿数据秒级查询?)

客户端向Zookeeper发起请求,读取Hbase:Meta数据,然后找到相应HRegionserver通过扫描HBase:Info:server下的值,定位到HRegionServer节点信息数据,然后HMaster active获取到HRegion节点信息查询Hregionserver中的BlockCache,有数据就返回,没有数据继续查询HRegion中列族的Store MemStore,如果有数据就返回,没有数据就通过布隆过滤器筛选掉不存在的数据,最后通过查询HFile中遍历的Rowkey值,然后先存入BlockCache以便下一次查询,最后返回数据到客户端。

image-20231120140307391

  • HBase写入流程

client访问zookeeper,通过扫描Hase:info:server并过滤获取hbase:meta所在HRegionServer节点信息,然后client先把元数据加载到内存中,再从内存中查询到rowkey所在的HRegion(HRegion所在的HRegionServer),cilent对RowKey所在的HRegion发起写入数据请求,建立连接后首先把DML操作写入到日志HLOG,然后再将数据更新到memestore中,当MemStore数据达到阈值后(默认128MB),创建新的Memstore,旧的MemStore将刷写一个独立的StoreFile(Hfile)并存放到HDFS中实现高可用,最后删除HLog中的历史数据。

image-20231120140748275

  • 数据刷写

MemStore数据达到128MB,先拍快照,为了防止刷写过程中更新数据同时在snapshot和MemStore中而造成后续处理的困难,所以再刷写期间需要持有UpdateLock,刷写成功后释放更新锁。如果出现了写的数据太大,HBASE会异步创建多个Memstore进行刷写,刷写成功后就销毁memstore,最终保留一个未刷写的Memstore。

Memstore占用内存达到128M,将进行数据刷写。

内存总和触发:HBase 为 HRegionServer 所有的 MemStore 分配了一定的写缓存,大小等于 hbase_heapsize(HRegionServer 占用的堆内存大小) * hbase.regionserver.global.memstore.size(默认为 0.4)例如:HBase 堆内存总共是 32G,MemStore 占用内存为:32 * 0.4 * 0.95 = 12.16G 将触发刷写。

日志阈值:HBase 使用了 WAL 机制(日志先行),当数据到达 HRegion 时是先写入日志的,然后再被写入到 MemStore。如果日志的数量越来越 大,这就意味着 MemStore 中未持久化到磁盘的数据越来越多。日志达到一定量时进行刷写。

定期刷写、更新频率、手动刷写

image-20231120140941742

  • 刷写策略

MemStore 所在的 HRegion 中其他的 MemStore 也是会被一起刷写的,Flush 一个列族时其它列族也会一起 Flush.

判断 HRegion 中每个 MemStore 的使用内存是否大于指定阀值,大于阀值的 MemStore 将会被刷写。阈值计 算公式: flushSizeLowerBound = max((long)128 / 3, 16) = 42

将 Region 中的 MemStore 按照 isSloppyMemStore 分到两个 HashSet 里面( sloppyStores 和 regularStores )然后判断 regularStores 里面是否有 MemStore 内存占用大于相关阀值的 MemStore,有的话就会对这些 MemStore 进行刷写,其他的 不做处理,这个阀值计算和 FlushAllLargeStoresPolicy 的阀值计算逻辑一致。

如果 regularStores 里面没有 MemStore 内存占用大于相关阀值的 MemStore,这时候就开始在 sloppyStores 里面寻找是否有 MemStore 内存占用大于相关阀值的 MemStore,有的话就会对这些 MemStore 进行刷写,其他的不做处理。

如果上面 sloppyStores 和 regularStores 都没有满足条件的 MemStore 需要刷写,这时候就将 FlushNonSloppyStoresFirstPolicy 策略久退化成Flush 一个列族时其它列族也会一起 Flush 策略了

七、数据合并

image-20231120141741170

 HBase 根据合并规模将压实 Compaction 分为了两类:Minor Compaction 和 Major Compaction。

Minor Compaction:快速让小文件合并成大文件,不做任何删除数据、多版本数据的清理工作,但是会 对 minVersion=0 并且设置 TTL 的过期版本数据进行清理。

Major Compaction:清理大文件不必要的数据,释放空间,清理三类无意义数据:被删除的数据、TTL 过期数据、版本号超过设定版本号的数据。

image-20231120142103050

image-20231120142124240

  • 文件合并策略

RatioBasedCompactionPolicy(基于比列的合并策略):从老到新逐一扫描 HFile 文件,满足以下条件之一停止扫描:当前文件大小 < 比当 前文件新的所有文件大小总和 * Ratio(高峰期1.2,非高峰期5),当前所剩候选文件数 <= 阈值(默认为3)。例如:当前文件 2G < 所有文件大 小总和 1G * 1.2,高峰期不合并,非高峰期合并;

ExploringCompactionPolicy 策略(默认策略):基于 Ratio 策略,不同之处在于 Ratio 策略找到一个合适文件集合就停止扫描,而 Exploring 策略会记录所有合适的文件集合,然后寻找最优解,待合并文件数最多或者待合并文件数相同的情况下文件较小的进行合并;

image-20231120142313005

  • 切分原因

数据分布不均匀: 同一 HRegionServer 上数据文件越来越大,读请求也会越来越多。一旦所有的请求都落在同一个 HRegionServer 上,尤其是很多热点数 据,必然会导致很严重的性能问题。

Compaction 性能损耗严重:Compaction 本质上是一个排序合并的操作,合并操作需要占用大量内存,因此文件越大,占用内存越多。

. 资源耗费严重:HBase 的数据写入量也是很惊人的,每天都可能有上亿条的数据写入不做切分的话一个热点 HRegion 的新增数据量就有可能几十G,用 不了多长时间大量读请求就会把单台 HRegionServer 的资源耗光。

  • 触发时机

,最小的分裂大小和 Table 的某个 HRegionServer 的 HRegion 个数有关,当 StoreFile 的大小大于以下公式得出的值的时候就会 Split。

image-20231120142720394

  • 切分流程

将一个 HRegion 切分为两个近似大小的子 HRegion,首先要确定切分点。切分操作是基于 HRegion 执行的,每个 HRegion 有多个 Store (对应多个 Column Famliy)。系统首先会遍历所有 Store,找到其中最大的一个,再在这个 Store 中找出最大的 HFile,定位这个文件中心位置对应的 RowKey,作为 HRegion 的切分点。

  • 开启切分事务

为了防止HRegionServer在进行切分时,因非正常原因造成数据丢失,切分线程会初始化一个 SplitTransaction 对象,从字面上就可以看出来 Split 流程是一个类似“事务”的过程,整个过程分为三个阶段: prepare - execute - rollback。

  • 切分优化

 对于预估数据量较大的表,需要在创建表的时候根据 RowKey 执行 HRegion 的预分区。通过 HRegion 预分区,数据会被均衡到多台机器 上,这样可以一定程度解决热点应用数据量剧增导致的性能问题。

八、表设计

  • 表的层级关系
namespace==>table==>rowkey==>column Family==>column Qualified
一个Hregion由多行RowKeys数据组成(跨列族,每个Hregion最好不要超过3个列族)
  • RowKey设计

唯一原则: 单主键、组合主键(注意顺序)

长度原则: 不要超过 16 个字节、对齐 RowKey 长度

散列原则: 反转 、加盐、 Hash

image-20231120143800074

  • 列族设计

追求原则:在合理范围内,尽可能的减少列族,不要超过3个。  

最优设计:将所有相关性很强的 key-value 都放在同一个列族。这样既能做到查询效率最高,也能保证尽可能少的访问不同的磁盘文件。  

控制长度:列族名的长度要尽量小,一个为了节省空间,一个为了加快效率,最好是一个字符,比如 d 表示 data 或 default,v 表示 value。

  • 写入优化

多 Table 并发写

在 HBae 中,客户端向集群中的 RegionServer 提交数据时(Put/Delete操作),首先会先写 WAL(Write Ahead Log)日志(即 HLog,一 个 HRegionServer 上的所有 HRegion 共享一个 HLog),当 WAL 日志写成功后,再接着写 MemStore,然后客户端被通知提交数据成功;如果 写 WAL 日志失败,客户端则被通知提交失败。这样做的好处是可以做到 HRegionServer 宕机后的数据恢复。

批量写:通过调用 Table.put(List) 方法 可以将指定的 RowKey 列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络 I/O 开销,这对于对数据实时性要求高,网络 传输 RTT(Round Trip Time) 高的情景下可能带来明显的性能提升。

  • 读取优化

image-20231120144635948

  • 缓存优化

对于频繁查询 HBase 的应用场景,可以考虑在应用程序和 HBase 之间做一层缓存系统,新的查询先去缓存查,缓存没有再去查 HBase。 例如 Redis。

九、HBase与Hive整合

拷贝hive根目录下中lib包下的 hive-hbase-handler-3.1.2.jar拷贝至HBase中的lib目录(三台机器都要)

cp /usr/local/hive/apache-hive-3.1.2-bin/lib/hive-hbase-handler-3.1.2.jar /usr/local/hbase/hbase-2.5.3/lib/

修改配置文件:修改 Hive(MetaStore 机器) 的配置文件 vim /opt/yjx/apache-hive-3.1.2-bin/conf/hive-site.xml ,配置 HBase 的 ZooKeeper 集 群。

image-20231120145120975

测试

$HBASE_HOME/bin/hbase shell  #登录hbase shell
  • HBase 创建 hbase_user 表并添加 info 列族。
# 创建 test 命令空间
create_namespace 'test'
# 创建 hbase_user 表并添加 info 列族
create 'test:hbase_user', 'info'
# 插入数据
put 'test:hbase_user', '1', 'info:name', 'zhangsan'
put 'test:hbase_user', '1', 'info:age', '18'
put 'test:hbase_user', '1', 'info:gender', 'male'
put 'test:hbase_user', '2', 'info:name', 'lisi'
put 'test:hbase_user', '2', 'info:age', '19'
put 'test:hbase_user', '2', 'info:gender', 'female'
put 'test:hbase_user', '3', 'info:name', 'wangwu'
put 'test:hbase_user', '3', 'info:age', '20'
put 'test:hbase_user', '3', 'info:gender', 'male'
  •  Hive 创建 hive_user 外部表并使用 HBase 序列化器。
hive  #进入hive命令界面
-- 创建 test 数据库
CREATE DATABASE IF NOT EXISTS test;
-- 创建 hive_user 外部表
CREATE EXTERNAL TABLE IF NOT EXISTS test.hive_user (
id string,
name string,
age string,
gender string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.hbase.HBaseSerDe'
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES('hbase.columns.mapping'=':key,info:name,info:age,info:gender')
TBLPROPERTIES('hbase.table.name'='test:hbase_user');
-- 通过查询测试是否整合成功
SELECT * FROM test.hive_user;
  • HBase缺点

在 HBase 中,HRegionServer 对“ZooKeeper 会话超时”的处理方式是“自杀”。而 HRegionServer 上“多个 HRegion 合写一个 WAL 到 HDFS” 的实现方式会使得“自杀”这一行为的成本较高,因为自杀之后 HRegionServer 重启时会拆分和重放 WAL。这就意味着假如整个 HBase 集群挂 了,想要将 HBase 重新给拉起来,时间会比较长。

HBase 是用 Java 实现的。GC 的存在,会使得 ZooKeeper 把正常运行的 HRegionServer 误判为死亡,进而又会引发 HRegionServer 的自杀;在其之上的 HRegion,需要其他的 HRegionServer 从 HDFS 上加载重放 WAL 才能提供服务。而这一过程,同样也是比较耗时的。在此期间内,HRegion 所服务的 Key 都是不可读写的。

十、Phoenix

Phoenix就是HBase的一个皮肤,封装了HBase中的shell命令,解放了开发者的双手,开发者通过SQL语句来操作HBase,从而降低学习成本和学习难度。

image-20231120150219663

  • 重客户端

Phoenix 是 HBase 之上的 SQL 层,它为 HBase 赋予了 NewSQL 的特性,支持了大多数的标准 SQL 特性,并提供了 JDBC 的访问接口,使得我们在应用程序中能够方便的集成使用

image-20231120150321333

  • 安装重客户端集群

Phoenix 是以 JDBC 驱动方式嵌入到 HBase 中的,在部署时只有一个 Jar 包,直接放到 HBase 的 lib 目录即可。

重客户端为: phoenix-server-hbase-2.5-5.1.3.jar

  • 将重客户端安装包上传至服务器。解压后将 phoenix-server-hbase-xx.jar 包拷贝至三台 HBase 的 lib 目录下
[root@node01 ~]# tar -zxvf phoenix-hbase-2.5-5.1.3-bin.tar.gz -C /opt/yjx/
[root@node01 ~]# rm phoenix-hbase-2.5-5.1.3-bin.tar.gz -rf
# 切换目录
[root@node01 ~]# cd /opt/yjx/phoenix-hbase-2.5-5.1.3-bin/
# 拷贝
[root@node01 phoenix-hbase-2.5-5.1.3-bin]# cp phoenix-server-hbase-2.5-5.1.3.jar /opt/yjx/hbase2.5.3/lib/
[root@node01 phoenix-hbase-2.5-5.1.3-bin]# scp phoenix-server-hbase-2.5-5.1.3.jar
root@node02:/opt/yjx/hbase-2.5.3/lib/
[root@node01 phoenix-hbase-2.5-5.1.3-bin]# scp phoenix-server-hbase-2.5-5.1.3.jar
root@node03:/opt/yjx/hbase-2.5.3/lib/

  • 修改配置文件

把phoenix中bin目录下的hbase-site.xml 文件修改。

<!-- 二级索引 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<!-- 开启 Schema 与 NameSpace 的映射 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>

修改HBase目下conf目录中的hbase-site.xml文件

<!-- 二级索引 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<!-- 开启 Schema 与 NameSpace 的映射 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
  • 将phoenix和hbase中的hbase-site.xml文件分发给其他节点相同目录下
[root@node01 ~]# scp /opt/yjx/hbase-2.5.3/conf/hbase-site.xml root@node02:/opt/yjx/hbase-2.5.3/conf/
[root@node01 ~]# scp /opt/yjx/hbase-2.5.3/conf/hbase-site.xml root@node03:/opt/yjx/hbase-2.5.3/conf/

[root@node02 ~]# scp -r root@node01:/opt/yjx/phoenix-hbase-2.5-5.1.3-bin/ /opt/yjx/
[root@node03 ~]# scp -r root@node01:/opt/yjx/phoenix-hbase-2.5-5.1.3-bin/ /opt/yjx/
  • 修改环境变量

三个节点修改环境变量 vim /etc/profile ,在文件末尾添加以下内容,修改完成后 source /etc/profile 重新加载环境变量。

export PHOENIX_HOME=/opt/yjx/phoenix-hbase-2.5-5.1.3-bin
export PATH=$PHOENIX_HOME/bin:$PATH
  • 启动

phoenix没有单独的启动服务,随着HBase服务的启动而启动。

  • 连接
[root@node01 ~]# sqlline.py node1,master,node3:2181
[root@node01 ~]# !quit  #退出

首次连接时会在 HBase 中自动生成 8 张表,如下:

image-20231120151218864

  • phoenix数据模型

Phoenix 在数据模型上是将 HBase 非关系型形式转换成关系型数据模型.

对于 Phoenix 来说,HBase 的 RowKey 会被转换成 Primary Key;Column Family 如果不指定默认为 0;Qualifier 转换成 表的字段名。

image-20231120151337976

phoenix会区分大小写,默认会把小写字符转为大写字符,如果想要保留小写字符必须加" ",要是原字符就加'单引号。

  • 查看所有表
!tables

image-20231120151712114

  • 创建Schema(数据库)
CREATE SCHEMA school;  #大写字母数据库名
CREATE SCHEMA "school"; #小写字母数据库名

image-20231120151810439

  • 创建表

在 Phoenix Shell 上建的表默认只有一个 HRegion,建表时要注意预分区(推荐使用 HBase 建表)。

# 创建完的表名和字段名都会自动转成大写,如需小写,需在建表时给表名和字段名前后加双引号
CREATE TABLE school.teacher (
tno INTEGER NOT NULL PRIMARY KEY,
tname VARCHAR,
age INTEGER
);
============================== 华丽的分割线 ==============================
CREATE TABLE "school"."teacher" (
"tno" INTEGER NOT NULL PRIMARY KEY,
"info"."tname" VARCHAR,
"info"."age" INTEGER
);

image-20231120151907175

  • 插入和更新记录(有记录就更新记录,无记录就插入记录)
UPSERT INTO school.teacher (tno, tname, age) VALUES (1, 'zhangsan', 18);
UPSERT INTO school.teacher (tno, tname, age) VALUES (2, 'lisi', 18);
UPSERT INTO school.teacher (tno, tname, age) VALUES (3, 'wangwu', 20);
============================== 华丽的分割线 ==============================
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (1, 'zhangsan', 18);
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (2, 'lisi', 18);
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (3, 'wangwu', 20);

Column Family (列族)如果不指定则为 0。

image-20231120152048040

  • 查看索引
!tables school.teacher
  • 删除数据库
DROP SCHEMA school;
DROP SCHEMA "school"

以上在 Phoenix 中的 DDL 和 DML 操作在 HBase 对应的表中也会同时触发,即操作通过 Phoenix 来操作 HBase。

  • 退出
!quit

十一、Phoenix二级索引

  • 同步索引
CREATE INDEX my_index ON SCHEMA_NAME.TABLE_NAME (BASICINFO."s1", BASICINFO."s2");
  • 异步索引(异步 Build 索引需要借助 MapReduce,创建异步索引语法和同步索引只差一个关键字:ASYNC。)
CREATE INDEX async_index ON SCHEMA_NAME.TABLE_NAME (BASICINFO."s1", BASICINFO."s2") ASYNC;

异步创建索引时,如果有新数据写入,会出现索引表数据丢失现象。创建完成后,数据正常写入不会丢失,所以,创建完成后可能需要往索引表补数据。

  • 创建全局索引

 全局索引适合读多写少的场景。如果使用全局索引,读数据基本不损耗性能所有的性能损耗都来源于写数据。数据 表的添加、删除和修改都会更新相关的索引表(数据删除了,索引表中的数据也会删除;数据增加了,索引表的数据也会 增加)。

注意:全局索引在默认情况下,查询语句中检索的列如果不在索引表中,Phoenix 不会使用索引表。除非使用 hint。

索引表也是表(在 Phoenix 端删除数据表时索引表会一起被删除)。

  • 语法(索引表中 RowKey 为 v3 + 原表 RowKey。索引原理:Scan + Filter 形成前缀过滤器。)
CREATE INDEX my_index ON my_table (v3);
SELECT * FROM my_index; #查询索引表

image-20231120152732554

  • 使用hit强制索引
SELECT /*+ INDEX(my_table local_index) */ v1 FROM my_table WHERE v3 = '100010010';
  • 删除索引
DROP INDEX my_index ON my_table;
  • 创建本地索引

本地索引适合写多读少的场景,或者存储空间有限的场景。和全局索引一样,Phoenix 也会在查询的时候自动选择是 否使用本地索引.本地索引因 为索引数据和原数据存储在同一台机器上,避免网络数据传输的开销,所以更适合写多的场景.

对于本地索引,查询中无论是否指定 hint,或者查询语句中检索的列是否在索引表中,都会使用索引表。相当 于之前的第二种将数据拿出来单独建一张表,并改变 RowKey。

  • 语法(索引表中 RowKey 为 v3 + 原表 RowKey)
CREATE LOCAL INDEX local_index ON my_table (v3); #创建索引
DROP INDEX local_index ON my_table; #删除索引
  • Covered Indexes 覆盖索引

覆盖索引是把原数据存储在索引数据表中,这样在查询时不需要再去 HBase 的原表获取数据,直接返回查询结果。

对于覆盖索引,查询时 SELECT 的列和 WHERE 的列都需要在索引表中出现。

  • 语法 (与全局索引差不多,相当于 v2 + v3 + v1 + 原 RowKey 作为 RowKey。表中加了一个列 v1)
CREATE INDEX cover_index ON my_table (v2, v3) INCLUDE (v1);
  • 函数索引 (使用函数索引时,使用hit强制索引也没有用)
CREATE INDEX func_index ON my_table (SUBSTR(v3, 5, 5)) INCLUDE (v1);
#截取v3列中 从索引5开始截取5个字符 (索引从1开始计数)
SELECT v1, SUBSTR(v3, 5, 5) FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+------------------+
|   V1   | SUBSTR(V3, 5, 5) |
+--------+------------------+
| value1 | 10006            |
+--------+------------------+

-- 删除函数索引
DROP INDEX func_index ON my_table;
  • 索引失效与重建

hoenix 端数据写入时,HBase 宕机或重启,导致写入中断,写入更新索引表失败,导致索引失效,很多情况下是重 启 HBase 没有停 Phoenix 服务导致索引数据不一致。

  • 查看索引

 执行 !tables 查看索引表状态,当 INDEX_ST 为 PENDING_DISABLE 时表示索引不可用,需要进行修复。  

也可在 SYSTEM.CATALOG 表中查看索引状。

一般索引失效 Phoenix 会自动修复,但是基本上修复不好。

SELECT
TABLE_NAME, -- 表名(索引表也是个表)
DATA_TABLE_NAME, -- 数据表(我们保存数据的表,不包括索引表,虽然他也存数据)
INDEX_TYPE, -- 索引类型
INDEX_STATE, -- 索引状态
INDEX_DISABLE_TIMESTAMP -- DISABLE 的时间
FROM SYSTEM.CATALOG
WHERE INDEX_TYPE IS NOT NULL;
+------------+-----------------+------------+-------------+-------------------------+
| TABLE_NAME | DATA_TABLE_NAME | INDEX_TYPE | INDEX_STATE | INDEX_DISABLE_TIMESTAMP |
+------------+-----------------+------------+-------------+-------------------------+
| MY_INDEX   | MY_TABLE        | 1          | a           | 0                       |
+------------+-----------------+------------+-------------+-------------------------+

INDEX_STATE 会有 a, w, x, b 等枚举。如果为 a, e 则为正常服务; x, d 则为索引挂起,表示不可使用需要重建或者修复; i 为不可用,但在自动修复; b 为重建中。

  • 手动修复(先找到失效的索引)
SELECT
TABLE_NAME, -- 表名(索引表也是个表)
DATA_TABLE_NAME, -- 数据表(我们保存数据的表,不包括索引表,虽然他也存数据)
INDEX_TYPE, -- 索引类型
INDEX_STATE, -- 索引状态
INDEX_DISABLE_TIMESTAMP -- DISABLE 的时间
FROM SYSTEM.CATALOG
WHERE INDEX_TYPE IS NOT NULL AND INDEX_STATE IN ('w', 'x', 'i', 'd');
  • 设置索引为 DISABLE
ALTER INDEX my_index ON my_table DISABLE;
  • 重建索引
ALTER INDEX IF EXISTS my_index ON my_table REBUILD;

总结:数据量小推荐方法二,生产推荐方法二和三,其中方法三有可能会重建失败。

十二、映射表

Phoenix 支持通过视图 VIEW 映射 HBase 中的表。

作用:把在HBase中创建的数据表,能与Phoenix相关联。

  • Phoenix 创建视图(namespace===>schema 列族、字段要一一对应,以及primary key=>rowkey)
CREATE VIEW "test"."t_user" (
user_id VARCHAR PRIMARY KEY,
"personal"."name" VARCHAR,
"personal"."gender" VARCHAR,
"personal"."age" VARCHAR,
"office"."phone" VARCHAR,
"office"."address" VARCHAR
);
  • Phoenix 删除视图
DROP VIEW "test"."t_user";

JDBC搭建

  • pom.xml
      <!-- Phoenix 核心包 -->
        <dependency>
            <groupId>org.apache.phoenix</groupId>
            <artifactId>phoenix-core</artifactId>
            <version>5.1.3</version>
        </dependency>
        <!-- Phoenix 与 HBase 的兼容包 -->
        <dependency>
            <groupId>org.apache.phoenix</groupId>
            <artifactId>phoenix-hbase-compat-2.5.0</artifactId>
            <version>5.1.3</version>
            <scope>runtime</scope>
        </dependency>
        <!-- JUnit 单元测试 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>
  • 编写代码(创建连接)
package com.yjxxt.phoenix;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
@DisplayName("Phoenix JDBC 测试类")
public class PhoenixJdbcTest {
    private static Connection connection = null;
    private static Statement statement = null;
    @BeforeEach
    public void init() throws IOException {
        try {
            Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
            // 配置 ZooKeeper 地址
            String zkUrl = "jdbc:phoenix:node01,node02,node03:2181/hbase";
            // 开启 Schema 与 NameSpace 的映射
            Properties properties = new Properties();
            properties.setProperty("phoenix.schema.isNamespaceMappingEnabled", "true");
            // 创建数据库连接
            connection = DriverManager.getConnection(zkUrl);
            // 创建 Statement 对象
            statement = connection.createStatement();
     //然后sql的执行写法与原生的jdbc一样  先写sql字符串  然后调用execute()、executeQuery()、executeUpdate执行方法
       } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
       }
   }
    @AfterEach
    public void destory() throws IOException {
        try {
            if (statement != null && !statement.isClosed()) {
                statement.close();
           }
            if (connection != null && !connection.isClosed()) {
                connection.close();
           }
       } catch (SQLException throwables) {
            throwables.printStackTrace();
       }
   }
}
posted @ 2023-11-20 16:19  戴莫先生Study平台  阅读(169)  评论(0编辑  收藏  举报