HBase概述
HBase是以 hdfs 为数据存储的,一种分布式、非关系型的、可扩展的 NoSQL 数据库
关系型数据库和非关系型数据库的区别:
关系型数据库和非关系型数据库是两种不同的数据库类型,它们在存储方式、数据结构、查询语言等方面存在显著差异。
存储方式和结构:
- 关系型数据库以二维表格形式存储数据,结构比较规整固定。
- 非关系型数据库的存储格式可以是key-value形式、文档形式、图片形式等,结构更加灵活和可扩展。
数据结构和表的关系:
- 关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织。在关系型数据库中,必须定义好表和字段结构后才能添加数据。
- 非关系型数据库一般不确保遵照ACID标准的数据储存系统,可以是文档或键值对等,结构更加灵活。
优点和应用场景:
- 关系型数据库易于理解和维护,使用SQL语言通用,可用于复杂查询,适用于需要事务支持和复杂查询的应用。
- 非关系型数据库速度快,效率高,使用灵活,适用于需要大规模数据的读写和高并发访问的应用。
代表产品:
- 关系型数据库的主要代表有SQL Server,Oracle, Mysql, PostgreSQL等。
- 非关系型数据库的代表包括MongoDB、Cassandra、Redis等。
总体来说,关系型数据库和非关系型数据库各有优势和局限,选择哪种类型的数据库取决于具体的应用需求和场景。
逻辑结构
存储数据稀疏,数据存储多维,不同的行具有不同的列;数据存储整体有序,按照RowKey的字典序排列
物理存储结构
物理存储结构即为数据映射关系,而在概念视图的空单元格,底层实际根本不存储
像row_key11的name列,底层不进行存储
数据模型
Name Space
命名空间,类似于关系型数据库的 database 概念,每个命名空间下有多个表;HBase 两个自带的命名空间,分别是
hbase 和 default
,hbase 中存放的是 HBase 内置的表,default表是用户默认使用的命名空间
Table
类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。因为数据存储是稀疏的,所以往HBase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景
Row
HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要
Column
HBase 中的每个列都由 **Column Family(列族)**和 Column Qualifier(列限定符)进行限定,例如
info:name,info:age
。建表时,只需指明列族,而列限定符无需预先定义
Time Stamp
用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase 的时间
Cell
由
{rowkey, column Family:column Qualifier, timestamp}
唯一确定的单元。cell 中的数据全部是字节码形式存储
总结:
- HBase 定义表时只需要声明列族即可,不需要声明具体的列,因为数据的存储是稀疏的;也因此能轻松应对字段变更的场景
- HBase是NoSql数据库,数据的查询只能通过RowKey 进行检索,不能通过SQL语句查询
- 通过哪些字段可以唯一确定一条数据? RowKey(行键)、Column Family(列族)、Column Qualifier(列限定符)、timestamp(数据写入HBase的时间)
HBase 基本架构
Master
实现类为 HMaster,通常部署在namenode上,负责监控集群中所有的 RegionServer 实例。主要作用如下:
(1)管理元数据表格 hbase:meta,接收用户对表格创建修改删除的命令并执行
(2)监控 region 是否需要进行负载均衡,故障转移和 region 的拆分。
通过启动多个后台线程监控实现上述功能:
①LoadBalancer 负载均衡器:
周期性监控 region 分布在 regionServer 上面是否均衡,由参数
hbase.balancer.period
控制周期时间,默认 5 分钟。
②CatalogJanitor 元数据管理器:
定期检查和清理 hbase:meta 中的数据
③MasterProcWAL master 预写日志处理器:
把 master 需要执行的任务记录到**预写日志 WAL **中,如果 master 宕机,让 backupMaster(高可用)读取日志继续工作
有关meta表格:
在meta表中进行元数据的存储,其表格式如下:
一共有三个列族:
查看表中具体存储的内容:
- RowKey:组成格式为
[table],[region start key],[region id])
,即表名,region 起始位置和 regionID - info:regioninfo 为 region 信息,存储一个 HRegionInfo 对象
- info:seqnumDuringOpen:打开表时的序列号;如果该属性未设置或设置为空字符串,则使用当前最大序列号作为起始序列号;否则,使用指定的序列号作为起始序列号
这个属性通常用于优化表的读取性能。通过将起始序列号设置为一个较大的值,可以跳过大量的早期数据,从而提高查询效率。但是,需要注意的是,如果指定的起始序列号过大,可能会导致错过一些重要的数据。因此,在使用该属性时需要谨慎选择起始序列号的值。
- info:server:当前 region 所处的 RegionServer 信息,包含端口号
- info:serverstartcode:当前 region 被分到 RegionServer 的起始时间
如果一个表处于切分的过程中,即 region 切分,还会多出两列 info:splitA 和 info:splitB,
存储值也是 HRegionInfo 对象,拆分结束后,删除这两列
什么时候客户端连接master?
在客户端对元数据进行操作的时候才会连接 master,如果对数据进行读写,直接连接zookeeper读取目录/hbase/meta-region-server 节点信息,会记录 meta 表格的位置。直接读取即可,不需要访问 master,这样可以减轻 master 的压力,相当于 master 专注 meta 表的写操作,客户端可直接读取 meta 表
Region Server
Region Server 实现类为 HRegionServer,通常部署在datanode上,主要作用如下:
(1)负责数据 cell 的处理,例如写入数据 put,查询数据 get 等
(2)拆分合并 region 的实际执行者,由 master 监控,由 regionServer 执行
如何理解Region?
Region是HBase中数据管理的基本单位,每个Region由其所属的表、第一行和最后一行组成,每个Region都有一个唯一的RegionID来标识;Region代表特定rowkey区间内的数据片段,每个Region存储着1到多个存储Store,每个Store对应Table中的一个ColumnFamily,并且每个Store中包含一个MemStore的写缓存
MemStore
写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好序后,等到达刷写时机才会刷写到 HFile,每次刷写都会形成一个新的 HFile,写入到对应的文件夹 store 中
每一个region的store都要有一个单独的写缓存
WAL(预写日志)
由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做 Write-Ahead logfile 的文件中,然后再写入 MemStore 中。所以在系统出现故障的时候,数据可以通过这个日志文件重建
BlockCache
读缓存,每次查询出的数据会缓存在 BlockCache 中,方便下次查询
Zookeeper
HBase 通过 Zookeeper 来做 master 的高可用、记录 RegionServer 的部署信息、并且存储有 meta 表的位置信息。
HBase 对于数据的读写操作是直接访问 Zookeeper 的,在 2.3 版本推出 Master Registry模式,客户端可以直接访问 master。使用此功能,会加大对 master 的压力,减轻对 Zookeeper的压力
HDFS
HDFS 为 Hbase 提供最终的底层数据存储服务,同时为 HBase 提供高容错的支持
HBase安装部署启动
安装流程
- 前置要求:安装部署Hadoop及zookeeper,并启动
- 将安装包解压到某一目录下,如
/opt/module
解压完成后文件目录如下:
- 配置环境变量:
vim /etc/profile.d/my_env.sh(自定义的环境变量文件)
,添加以下内容:
#HBASE_HOME
export HBASE_HOME=/opt/module/hbase #hbase的安装路径
export PATH=$PATH:$HBASE_HOME/bin
使用source指令使环境变量生效:
source /etc/profile.d/my_env.sh
修改配置文件:(配置文件文档:https://hbase.apache.org/book.html#config.files)
1.修改
hbase-env.sh
文件,添加:
export HBASE_MANAGES_ZK=false
(意为不需要使用hbase中自身的zookeeper)
2.修改
hbase-site.xml
文件:
<!--开启分布式集群模式-->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!--配置zookeeper服务器-->
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
<!--HBase数据持久化存储的路径(HDFS上)-->
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop102:8020/hbase</value>
</property>
3.修改
regionservers
文件,添加以下内容(部署
regionservers
的服务器):
hadoop102
hadoop103
hadoop104
4.解决 HBase 和 Hadoop 的 log4j 兼容性问题,修改 HBase 的 jar 包,使用 Hadoop 的 jar 包:
mv /opt/module/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar /opt/module/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar.bak
.bak是备份文件,这里将hbase的log4j设置为备份,就会自动使用hadoop的log4j文件
启动
- 单点启动:
bin/hbase-daemon.sh start master
;bin/hbase-daemon.sh start regionserver
- 群启:
bin/start-hbase.sh
- 停止:
bin/stop-hbase.sh
- 通过jps查看是否启动成功:
- 启动成功后查看 HBase 管理页面(默认端口16010):
http://hadoop102:16010
注意:hbase安装过程中并没有指定master部署在哪台服务器上,而是在哪台服务器上群启或单点启动master,master就在哪台服务器上工作;
高可用
- 在 conf 目录下创建
backup-masters
文件:touch conf/backup-masters
- 在 backup-masters 文件中配置高可用 HMaster 节点(hadoop103):
echo hadoop103 > conf/backup-masters
- 重启 hbase
HBase Shell
进入客户端命令行:
bin/hbase shell
查看帮助命令:
help
主要使用的命令有 namespace 命令空间相关,DDL 创建修改表格,DML 写入读取数据
namespace
创建命名空间:
create_namespace name
查看所有的命名空间:
list_namespace
查看命令如何使用:
help 'create_namespace'
DDL
创建表
create 'ns1:t1', {NAME => 'f1', VERSIONS => 5}
ns1:命名空间;t1:表名;{}表示一个列族,NAME:列族名称,VERSION:数据维护的版本数(如果不写默认为1
注意:如果不设置命名空间,默认在default命名空间下创建表;
查看create语法:
help 'create'
查看表
list
:可以看到所有的表及其所在的命名空间
describe
:查看一个表的详情
注意:如果不写命名空间,默认从default命名空间下查找表;
修改表
alter 'ns1:t1', {NAME => 'f1', VERSIONS => 3}
修改和添加列族都通过
alter
实现;
如果该列族之前已经存在,则覆盖之前的信息来进行修改;如果不存在,则新增列族
删除某一列族:
alter 'student1', NAME => 'f1', METHOD => 'delete'
或者:
alter 'student1', 'delete' => 'f1'
删除表
shell 中删除表格,需要先将表格状态设置为不可用:
disable 'student1'
然后再删除表:
drop 'student1'
DML
写入数据
put
:
r1:行键值;
c1:cell名称,一般是"列族名:列名"的格式
ts1:timestamp时间戳,推荐不写,默认使用当前的系统时间
注意:如果重复写入相同 rowKey,相同列的数据,会写入多个版本进行覆盖
读取数据
**
get
**
获取某一行的数据:
get 'ns1:t1', 'r1'
根据列进行过滤:
get 't1', 'r1', {COLUMN => 'c1'}
这里的'c1'是
列族:列限定符
的形式
多列过滤:
get 't1', 'r1', {COLUMN => ['c1', 'c2', 'c3']}
指定时间范围进行过滤:
get 't1', 'r1', {TIMERANGE => [ts1, ts2]}
指定版本进行过滤:
get 't1', 'r1', {COLUMN => 'c1', VERSIONS => 4}
最多能够读取当前列族设置的维护版本数条数据
比如说,bigdata:student的info这一列族设置的维护版本数为5
接下来向同一RowKey中依次插入6条数据:
put 'bigdata:student','1001','info:name','zhangsan'
put 'bigdata:student','1001','info:name','lisi'
put 'bigdata:student','1001','info:name','wangwu'
put 'bigdata:student','1001','info:name','4'
put 'bigdata:student','1001','info:name','5'
put 'bigdata:student','1001','info:name','6'
接下来查询info:name这一cell中的数据:
get 'bigdata:student','1001' , {COLUMN => 'info:name', VERSIONS => 1}
可以看到value为6,即最新插入的数据;
如果VERSIONS为5,则可以查询到最新的5条数据:
但VERSIONS再增加,也查询不到'zhangsan'这条数据了,因为族设置的维护版本数为5
**
scan
**
用于扫描数据,可以读取多行数据;可以通过startRow 和stopRow 来控制读取的数据,默认范围左闭右开
scan 'bigdata:student',{STARTROW => '1001',STOPROW => '1002'}
删除数据
delete
: 表示删除一个版本的数据,即为 1 个 cell,不填写版本默认删除最新的一个版本
语法:
**delete 'bigdata:student','1001','info:name'**
deleteall
:表示删除所有版本的数据,即为当前行当前列的多个 cell
语法:
deleteall 'bigdata:student','1001','info:name'
注意:执行命令会标记数据为要删除,不会直接将数据彻底删除,删除数据只在特定时期清理磁盘时进行
HBase数据读写流程
写流程
(1)首先访问 zookeeper,获取 hbase:meta 表由哪个 Region Server管理;
meta表的修改是由master负责的,而所有regionServer都可以读取meta表中的数据
(2)访问对应的 Region Server,获取 hbase:meta 表,将其缓存到连接中,作为连接属性 MetaCache,由于 Meta 表格具有一定的数据量,导致了创建连接比较慢;之后使用创建的连接获取 Table,这是一个轻量级的连接,只有在第一次创建的时候会检查表格是否存在而访问 RegionServer,之后在获取 Table 时不会访问 RegionServer;
连接需要保存读取的meta表,所以创建的连接是重量级的
(3)调用Table的put方法写入数据,此时还需要解析RowKey,对照缓存的MetaCache,查看具体写入的位置有哪个 RegionServer;
(4)将数据顺序写入(追加)到 WAL,此处写入是直接落盘的,并设置专门的线程控制 WAL 预写日志的滚动(类似 Flume);
(5)根据写入命令的 RowKey 和 ColumnFamily 查看具体写入到哪个 MemStore,并且在 MemStore中排序;
(6)向客户端发送 ack;
(7)等达到 MemStore 的刷写时机后,将数据刷写到对应的 store 中
MemStore Flush
MemStore 刷写由多个线程控制,条件互相独立
文件大小限制
- 当某个 memstroe 的大小达到了
** hbase.hregion.memstore.flush.size(默认值 128M)**
,其所在 region 的**所有 memstore **都会刷写 - 当 memstore 的大小达到了
**hbase.hregion.memstore.flush.size*hbase.hregion.memstore.block.multiplier(默认值4)**
时,会向store刷写,同时阻止继续往该 memstore 写数据
占用内存限制
由 HRegionServer 中的属性 MemStoreFlusher 内部线程 FlushHandler 控制。标准为LOWER_MARK(低水位线)和 HIGH_MARK(高水位线),意义在于避免写缓存使用过多的内存造成 OOM(Out Of Memory)
OOM:当Java虚拟机因为没有足够的内存来为对象分配空间,并且垃圾回收器也已经没有空间可回收时,就会抛出这个错误
- 当 region server 中 memstore 的总大小达到低水位线
**java_heapsize**** * hbase.regionserver.global.memstore.size(默认值 0.4)* hbase.regionserver.global.memstore.size.lower.limit(默认值 0.95**
),region 会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写。直到 region server中所有 memstore 的总大小减小到上述值以下 - 当 region server 中 memstore 的总大小达到高水位线
**java_heapsize hbase.regionserver.global.memstore.size(默认值 0.4)**
时,会同时阻止继续往所有的 memstore 写数据
用时限制
为了避免数据过长时间处于内存之中,到达自动刷写的时间,也会触发 memstoreflush
由HRegionServer 的属性 PeriodicMemStoreFlusher 控制进行,由于重要性比较低,5min才会执行一次(注意是执行一次判断是否需要刷写)
自动刷新的时间间隔由该属性进行配置 :
**hbase.regionserver.optionalcacheflushinterval(默认1 小时)**
文件数量限制
当 WAL 文件的数量超过
**hbase.regionserver.max.logs**
,region 会按照时间顺序依次进行刷写,直到 WAL 文件数量减小到 hbase.regionserver.max.logs 以下(该属性名已经废弃,现无需手动设置,可以自动调整,最大值为 32)
读流程
HFile 结构
HFile 是存储在 HDFS 上面每一个 store 文件夹下实际存储数据的文件
里面存储多种内容。包括数据本身(keyValue 键值对)、元数据记录、文件信息、数据索引、元数据索引和一个固定长度的尾部信息(记录文件的修改情况)
在HFile中,数据(键值对)按照块大小(默认 64K)保存在文件中,数据索引按照块创建,块越多,索引越大。每一个 HFile 还会维护一个布隆过滤器
布隆过滤器的主要功能是判断一个元素是否在一个集合中——每有一种 key,就在对应的位置标记,读取时可以大致判断要 get 的 key 是否存在 HFile 中
KeyValue 内容如下:
- rowlength -----------→ key 的长度
- row -----------------→ key 的值
- columnfamilylength --→ 列族长度
- columnfamily --------→ 列族
- columnqualifier -----→ 列名
- timestamp -----------→ 时间戳(默认系统时间)
- keytype -------------→ Put
如何查看hfile的元数据内容?
bin/hbase hfile -m -f /hbase/data/命名空间/表名/regionID/列族/HFile名
例如:
bin/hbase hfile -m -f /hbase/data/why/student/fdccb396f319649ec0faa839c70fde56/info/29823e6596434a87b619c8e25d534c8e
可以查看到以下内容:
读流程
首先访问zk创建连接,与写流程完全相同;
(1)创建 Table 对象发送** get **请求
(2)优先访问 Block Cache,查找是否之前读取过,并且可以读取** HFile 的索引信息和布隆过滤器**
(3)不管读缓存中是否已经有数据了(可能已经过期了),都需要再次读取写缓存和store中的文件
(4)最终将所有读取到的数据合并版本,按照get的要求返回即可
合并读取数据优化
每次读取数据都需要读取三个位置(Block Cache、Mem Store和Store),最后进行版本的合并。效率会非常低,所有系统需要对此优化:
(1)HFile 带有索引文件,读取对应 RowKey 数据会比较快。
(2)Block Cache 会缓存之前读取的内容和元数据信息,如果 HFile** 没有发生变化(记录在 HFile 尾信息中(Trailer)),则不需要再次读取**。
(3)使用布隆过滤器能够快速过滤当前 HFile 不存在需要读取的 RowKey,从而避免读取文件(布隆过滤器使用 HASH 算法,不是绝对准确的,出错会造成多扫描一个文件,对读取数据结果没有影响)
StoreFile Compaction
由于 memstore 每次刷写都会生成一个新的 HFile,文件过多读取不方便,所以会进行文件的合并,清理掉过期和删除的数据,会进行 StoreFile Compaction
Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction:
Minor Compaction会将临近的若干个较小的 HFile 合并成一个较大的 HFile,并清理掉部分过期和删除的数据,由系统自动控制
Major Compaction 会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉所有过期和删除的数据,由参数
hbase.hregion.majorcompaction
控制,默认 7 天
Minor Compaction 控制机制
参与到小合并的文件需要通过参数计算得到,有效的参数有 5 个
(1)hbase.hstore.compaction.ratio(默认 1.2F)合并文件选择算法中使用的比率。
(2)hbase.hstore.compaction.min(默认 3) 为 Minor Compaction 的最少文件个数。
(3)hbase.hstore.compaction.max(默认 10) 为 Minor Compaction 最大文件个数。
(4)hbase.hstore.compaction.min.size(默认 128M)为单个Hfile文件大小最小值,小于这
个值会被合并
(5)hbase.hstore.compaction.max.size(默认 Long.MAX_VALUE)为单个Hfile文件大小最大
值,高于这个值不会被合并
小合并机制为拉取整个 store 中的所有文件,做成一个集合。之后按照从旧到新的顺序遍历。判断条件为:
① 过小合并,过大不合并
② 文件大小/ hbase.hstore.compaction.ratio < (剩余文件大小和) 则参与压缩。
所以把比值设置过大,如设置为10,会导致最终合并为1个特别大的文件,相反设置为0.4,会最终产生 4 个storeFile。不建议修改默认值
③ 满足压缩条件的文件个数达不到个数要求(3 <= count <= 10)则不压缩
Region Split
Region 切分分为两种,创建表格时候的预分区即自定义分区,同时系统默认还会启动一个切分规则,避免单个 Region 中的数据量太大
预分区(自定义分区)
每一个 region 维护着 startRow 与 endRowKey,如果加入的数据符合某个 region 维护的rowKey 范围,则该数据交给这个 region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase 性能
1.手动设置:
create 'staff1','info', SPLITS => ['1000','2000','3000','4000']
2.生成 16 进制序列预分区:
create 'staff2','info',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
NUMREGIONS参数表示预分区的region个数,其值一般按照每个region使用6~8 GB的存储量来计算设定。当表格的数据大小达到
hbase.max.filesize
属性中定义的阈值(默认10GB)时,表格将会进行split操作,分裂成与NUMREGIONS设定数量相同的区域
SPLITALGO参数则是用来指定Rowkey分割的算法,它决定了如何根据rowkey的范围来划分regions。例如,可以使用HexStringSplit算法,该算法会将数据从“00000000”到“FFFFFFFF”之间的数据长度按照n等分后计算出每一段的实际rowkey和结束rowkey,以此作为拆分点
3.使用 JavaAPI 创建预分区:
admin.createTable方法中可以填写分区参数
系统拆分
Region 的拆分是由 HRegionServer 完成的,在操作之前需要通过 ZK 汇报 master,修改
对应的 Meta 表信息添加两列
**info:splitA 和 info:splitB **
信息。之后需要操作 HDFS 上面对
应的文件,按照拆分后的 Region 范围进行标记区分;
实际操作为创建文件引用,不会挪动数据。刚完成拆分的时候,两个 Region 都由原先的 RegionServer 管理。之后汇报给 Master,由Master将修改后的信息写入到Meta表中。等待下一次触发负载均衡机制,才会修改Region的管理服务者,而数据要等到下一次压缩时,才会实际进行移动
HBase2.0之后最新的系统拆分策略:
如果当前 RegionServer 上该表只有一个 Region,按照
2 * hbase.hregion.memstore.flush.size(128M)
分裂,否则按照
hbase.hregion.max.filesize(10G)
分裂
HBase 优化
RowKey设计
一条数据的唯一标识就是 rowkey,那么这条数据存储于哪个分区,取决于 rowkey 处于哪个一个预分区的区间内,设计 rowkey的主要目的 ,就是让数据均匀的分布于所有的 region中,在一定程度上防止数据倾斜
RowKey设计常用策略:
1)生成随机数、hash、散列值
2)时间戳反转 (使得新数据能写在前面)
注意这里的时间戳反转不是简单的字符串reverse,而是用9999999999999减去当前的时间戳(hbase的timestamp是13位的)
3)字符串拼接
实例分析
现有数据如下所示:
需求:使用 hbase 存储下列数据,要求能够通过 hbase 的 API 读取数据完成两个统计需求
- 统计张三在 2021 年 12 月份消费的总金额
- 统计所有人在 2021 年 12 月份消费的总金额
那么如何来设计RowKey?
需求1分析
对于需求1,要统计某个人在某个月份的消费总金额,可以直接使用scan指令扫描所有的消费记录,然后进行累加即可;要使用scan指令,需要明确其startRow和endRow;
需要从user和date两个维度去考虑,RowKey设计如下:
scan : startRow -> zhangsan^A^A^A^A-2021-12
endRow -> zhangsan^A^A^A^A-2021-12.
- 为了避免扫描数据混乱,需要解决字段长度不一致的问题,可以使用相同阿斯卡码值的符号进行填充
框架底层填充使用的是阿斯卡码值为 1 的^A
- 最后的日期结尾处需要使用阿斯卡码略大于'-'的值
所以最终的RowKey设计为:
rowKey: user-date(yyyy-MM-dd HH:mm:SS)
附:ASCII码表如下:
需求2分析
需求2中要求统计所有用户12月份的消费金额,与1相比只用考虑date这一个维度
我们会发现如果采用需求1中设计的RowKey,会完全无法确定scan的范围;这也是RowKey设计的一大特点:适用性强 泛用性差 能够完美实现一个需求 但是不能同时完美实现多个需要;
所以需要对1中的设计进行调整,调整原则如下:可枚举的放在前面
比如说,该需求中,date是可枚举的,而user是不可枚举的,所以设计时date应该在前面
最终设计如下:
rowKey 设计格式 => date(yyyy-MM)user^A^A^A^A-date(-dd hh:mm:ss ms)
(1)统计张三在 2021 年 12 月份消费的总金额
scan: startRow => 2021-12zhangsan^A^A^A^A
stopRow => 2021-12zhangsan^A^A^A^A.
(2)统计所有人在 2021 年 12 月份消费的总金额
scan: startRow => 2021-12
stopRow => 2021-12.
预分区优化
针对以上需求,可以进行预分区优化,预分区的分区号同样需要遵守 rowKey 的 scan 原则。所有必须添加在 rowKey 的最前面,前缀为最简单的数字。同时使用 hash 算法将用户名和月份拼接决定分区号
使用hash算法的原因:单独使用用户名会造成单一用户所有数据存储在一个分区,使用hash算法可以防止数据倾斜
设计步骤:
1、添加预分区优化(一共120个分区)
startKey stopKey
001
001 002
002 003
...
119 120
2.获取分区号:分区号=> hash(user+date(MM)) % 120
3.分区号填充 如果得到 1 => 001
4.rowKey 设计格式 => 分区号-date(yyyy-MM)-user^A^A^A^A-date(-dd hh:mm:ss ms)
缺点:实现需求 2 的时候,由于每个分区都有 12 月份的数据,需要扫描 120 个分区
改进:提前将分区号和月份进行对应,如下:
1、对应方法:
000 到 009 分区 存储的都是 1 月份数据
010 到 019 分区 存储的都是 2 月份数据
...
110 到 119 分区 存储的都是 12 月份数据
2、示例分析:
假设是9月份的数据
分区号=> hash(user+date(MM)) % 10 + 80
分区号填充 如果得到 85 => 085
得到9月份所有人的数据
扫描 10 次
scan: startRow => 0802021-12
stopRow => 08902021-12.
...
startRow => 0822021-12
stopRow => 0822021-12.
..
startRow => 0892021-12
stopRow => 0892021-12.
参数优化
Zookeeper 会话超时时间
文件:hbase-site.xml
属性:
zookeeper.session.timeout
解释:默认值为 90000 毫秒(90s)。当某个 RegionServer 挂掉,90s 之后 Master 才能察觉到。可适当减小此值,尽可能快地检测 regionserver 故障,可调整至 20-30s。
同时可以调整重试时间和重试次数
hbase.client.pause
(默认值 100ms)
hbase.client.retries.number
(默认 15 次)
设置 RPC 监听数量
文件:hbase-site.xml
属性:
hbase.regionserver.handler.count
解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值
手动控制 Major Compaction(大合并)
文件:hbase-site.xml
属性:
hbase.hregion.majorcompaction
解释:默认值:604800000 秒(7 天), Major Compaction 的周期,若关闭自动 MajorCompaction,可将其设为 0
如果关闭一定记得自己手动合并,因为大合并非常有意义
优化 HStore 文件大小
文件:hbase-site.xml
属性:
hbase.hregion.max.filesize
解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile
优化 HBase 客户端缓存
文件:hbase-site.xml
属性:
hbase.client.write.buffer
解释:默认值 2097152bytes(2M)用于指定 HBase 客户端缓存,增大该值可以减少 RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的
指定 scan.next 扫描 HBase 所获取的行数
文件:hbase-site.xml
属性:
hbase.client.scanner.caching
解释:默认值:2147483647(nteger.MAX_VALUE);用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大
BlockCache 占用 RegionServer 堆内存的比例
文件名:hbase-site.xml
属性:
hfile.block.cache.size
解释:默认 0.4,读请求比较多的情况下,可适当调大
MemStore 占用 RegionServer 堆内存的比例
文件:hbase-site.xml
属性:
hbase.regionserver.global.memstore.size
解释:默认 0.4,写请求较多的情况下,可适当调大
HBase 使用经验法则
官方给出了权威的使用法则:
(1)Region 大小控制 10-50G
(2)cell 大小不超过 10M(性能对应小于 100K 的值有优化),如果使用 mob(Medium-sized-Objects 一种特殊用法)则不超过 50M。
(3)1 张表有 1 到 3 个列族,不要设计太多。最好就 1 个,如果使用多个尽量保证不会同时读取多个列族。
(4)1 到 2 个列族的表格,设计 50-100 个 Region。
(5)列族名称要尽量短,不要去模仿 RDBMS(关系型数据库)具有准确的名称和描述。
(6)如果 RowKey 设计时间在最前面,会导致有大量的旧数据存储在不活跃的 Region中,使用的时候,仅仅会操作少数的活动 Region,此时建议增加更多的 Region 个数。
(7)如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存不会占用太多的内存。
说明:本学习笔记根据基于尚硅谷课程进行整理,课程链接:hbase
未完待续~
版权归原作者 THE WHY 所有, 如有侵权,请联系我们删除。