一、HBase简介
Hadoop Database是一个高可靠性、高性能、面向列、可伸缩、实时读写的分布式数据库。
利用Hadoop的HDFS作为其文件存储系统,利用zookeeper作为其分布式协调服务主要用来存储半结构化或非结构化的松散数据。
二、HBase数据模型
HBase是一个稀疏的、分布的、多维、排序的映射,它以行键、列簇、列名、和时间戳为索引。
2.1 NameSpace
命名空间类似于关系型数据库中数据库的概念,它其实是表的逻辑分组。
2.2 Table
Hbase的Table由多个行组成

2.3 RowKey
RowKey用来检索记录的主键,是一行数据的唯一标识
RowKey可以是任意字符串最大长度是64KB,以字节数组保存
存储时,数据按照Row Key的字典序排序,设计RowKey时要充分考虑排序存储这个特性,将经常读取的行存放到一起。
2.4 Colum Family:
列族在物理上包含了许多列与列的值,每个列族都有一些存储的属性可配置。
将功能相近的列存放到同一个列族中,相同列族中的列会存放在同一个store中
列族一般需要在创建表的时候声明,一般一个表中的列族不超过3个
列隶属于列族,列族隶属于表。
2.5 ColumQualifier
列族的限定词,理解为列的唯一标识。但是列标识是可以改变的,因此每一行会有不同的列标识
使用的时候必须 列族:列
列可以根据需求动态添加或删除,同一个表中的不同行的数据列都可以不同
2.6 Timestamp
通过rowkey、columFamily、columqualifier确定一个存储单元通过时间戳来索引
每个cell中,不同版本的数据按照时间顺序倒叙排序,即最新的数据排到最前面。
为了避免数据存在过多版本中造成管理负担,HBase提供了俩种数据版本回收方式
一是存储数据的最后n个版本
二是保存最近一段时间的版本
2.7 cell
Cell是由row columFamily、columQualifier、version组成
cell中数据没有类型,全部是字节码存储的
因为HDFS上的数据是字节数组

三、HBase架构模型
HBase有三个主要部分组成:客户端库,主服务器,区域服务器

3.1 Client
客户端负责发送请求到数据库,发送的请求包括
DDL 数据库定义语言(表的建立,删除,添加删除列族,控制版本)
DML 数据库操作语言(增删改)
DQL 数据库查询语言(查询--全表扫描--基于主键--基于过滤器)
client中维护着一些cache来加快对Hbase的访问,比如regione的位置信息
3.2 HMaster
HBase的主节点,HMaster也可以实现高可用(active-standby)
通过zookeeper来维护主副节点的切换
为region server分配region并负责region server的负载均衡
管理用户对table的DDL操作
当HRegion Server下线的时候,HMaster会将当前HRegion Server的Region转移到其他的RegionServer上
3.3 zookeeper
保证任何时候集群只有一个master
存储所有region的选址入口,存储所有的元数据信息
实时监控RegionServer的状态,将RegionServer的上线和下线实时通知给Master
存储Hbase的schame,包括有那些table,每个table上有哪些Colum family
3.4 HRegionServer
RegionServer属于HBase的具体数据管理者
RegionServer维护master分配给它的region,处理这些region的io请求
会实时和master保持心跳,汇报当前节点的信息
当接收到master命令创建表的时候,分配一个region对应一张表。
regionServer负责切分在运行过程中变得过大的region
当客户端发送DML或DQL的时候,HRegionServer负责和客户端建立连接
当意外关闭的时候当前regionServer的region会被其他regionServer管理
3.5 HRegion
HRegion是HBase分布式存储和负载均衡的最小单元。最小单元就是表示不同的HRegion可以分布在不同的HRegionServer上
HBase会自动把表拆分成多个Region,每个Region会保存表里边某段连续的数据

每个表一开始只有一个region,一个region只属于一张表,随着数据的不断写入,region不断增大,Region的大小达到10G的时候会被平分
当table的行不断增多,就会有越来越多的region,这样一张完整的表就会被保存到多个region Server中

为了防止前期的数据都集中在一个HRegionServer中,我们可以根据自己的业务进行预分区

3.6 store
HRegion是表获取和分布的基本单位,由一个或者多个store组成,每个store保存一个columsfamily
每个store又由一个memstore和0或多个storeFile组成
HFile是Hbase在HDFS中存储数据的格式,它包含多层索引,这样在HBase检索数据的时候就不用完全加载整个文件了
四、HBase读写流程
HBase读取数据的流程
01Client
访问
zookeeper
,获取
hbase:meta
所在
RegionServer
的节点信息
02Client
访问
hbase:meta
所在的
RegionServer
,获取
hbase:meta
记录的元数据后先加载到内存
中,然后再从内存中根据需要查询的
RowKey
查询出
RowKey
所在的
Region
的相关信息(
Region
所
在
RegionServer
)
03Client
访问
RowKey
所在
Region
对应的
RegionServer
,发起数据读取请求
04RegionServer
构建
RegionScanner
(需要查询的
RowKey
分布在多少个
Region
中就需要构建多少
个
RegionScanner
),用于对该
Region
的数据检索
05RegionScanner
构建
StoreScanner
(
Region
中有多少个
Store
就需要构建多少个
StoreScanner
,
Store
的数量取决于
Table
的
ColumnFamily
的数量),用于对该列族的数据检索
06
多个
StoreScanner
合并构建最小堆(已排序的完全二叉树)
StoreHeap:PriorityQueue
07StoreScanner
构建一个
MemStoreScanner
和一个或多个
StoreFileScanner
(数量取决于
StoreFile
数量)
08
过滤掉某些能够确定所要查询的
RowKey
一定不在
StoreFile
内的对应的
StoreFileScanner
或
MemStoreScanner
09
经过筛选后留下的
Scanner
开始做读取数据的准备,将对应的
StoreFile
定位到满足的
RowKey
的
起始位置
10
将所有的
StoreFileScanner
和
MemStoreScanner
合并构建最小堆
KeyValueHeap:PriorityQueue
,排序的规则按照
KeyValue
从小到大排序
11
从
KeyValueHeap:PriorityQueue
中经过一系列筛选后一行行的得到需要查询的
KeyValue
。
HBase写入数据流程
首先客户端和
RegionServer
建立连接
然后将
DML
要做的操作写入到日志
wal-log
然后将数据的修改更新到
memstore
中,然后本次操作结束
一个
region
由多个
store
组成,一个
store
对应一个
CF
(列族)
,store
包括位于内存中的
memstore
和位于磁盘的
storefile,
写操作先写入
memstore.
当
memstore
数据写到阈值之后,创建一个新的
memstore
旧的
memstore
写成一个独立的
storefile,regionserver
会启动
flashcache
进程写入
storefile
,每次
写入形成单独的一个
storefile,
存放到
hdfs
当
storefile
文件的数量增长到一定阈值后,系统会进行合并(
minor compaction
、
major
compaction
)
在合并过程中会进行版本合并和删除工作,形成更大的
storefile
当一个
region
所有
storefile
的大小和数量超过一定阈值后,会把当前的
region
分割为两个,并由
hmaster
分配到相应的
regionserver
服务器,实现负载均衡
Store
负责管理当前列族的数据
1
memstore+n
storefile
当我们进行数据
DML
的时候
,
以插入数据为例
我们会将数据先存储到
memStore
中,当
memStore
达到阈值(
128M
)
首先创建一个新的
memstore
然后会将
memStore
中的数据写成一个
storefile,storefile
会存储到
hdfs
上
(hfile)
随着时间的推移:
HFile
中会存放大量的失效数据
(
删除,修改
)
会产生多个
HFile
等达到阈值(时间、数量)会进行合并
多个
HFile
合并成一个大的
HFile
合并会触发连锁反应,相邻的
store
也会进行合并
在
Hbase
中,表被分割成多个更小的块然后分散的存储在不同的服务器上,这些小块叫做
Regions
,存放
Regions
的地方叫做
RegionServer
。
Master
进程负责处理不同的
RegionServer
之间
的
Region
的分发。在
Hbase
实现中
HRegionServer
和
HRegion
类代表
RegionServer
和
Region
。
HRegionServer
除了包含一些
HRegions
之外,还处理两种类型的文件用于数据存储
HLog
预写日志文件,也叫做
WAL(write-ahead log)
HFile
是
HDFS
中真实存在的数据存储文件
五、数据刷写
5.1.
触发时机
Region
中所有
MemStore
占用的内存超过相关阈值
hbase.hregion.memstore.flush.size
参数控制,默认为
128MB
如果我们的数据增加得很快,
达到了
hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier
的大小,
hbase.hregion.memstore.block.multiplier
默认值为
4
,
也就是
128*4=512MB
的时候,
那么除了触发
MemStore
刷写之外,
HBase
还会在刷写的时候同时阻塞所有写入该
Store
的
写请求!
整个
RegionServer
的
MemStore
占用内存总和大于相关阈值
HBase
为
RegionServer
所有的
MemStore
分配了一定的写缓存(),大小等于
hbase_heapsize
(
RegionServer
占用的堆内存大小)
*
hbase.regionserver.global.memstore.size(
默认值是
0.4)
。
如果整个
RegionServer
的
MemStore
占用内存总和大于阈值将会触发
MemStore
的刷写。
hbase.regionserver.global.memstore.size.lower.limit
(默认值为
0.95
)
* MAX_SIZE
例如:
HBase
堆内存总共是
32G
,
MemStore
占用内存为:
32 * 0.4 * 0.95 = 12.16G
将触发
刷写
如果达到了
RegionServer
级别的
Flush
,当前
RegionServer
的所有写操作将会被阻塞,这
个阻塞可能会持续到分钟级别。
WAL
数量大于相关阈值
数据到达
Region
的时候是先写入
WAL
,然后再被写到
Memstore
。如
果
WAL
的数量越来越大,这就意味着
MemStore
中未持久化到磁盘的数据越来越多。
当
RS
挂掉的时候,恢复时间将会变得很长,所以有必要在
WAL
到达一定的数量时进行一次
刷写操作
定期自动刷写
hbase.regionserver.optionalcacheflushinterval
默认值
3600000
(即
1
小时),
HBase
定期
Flush
所有
MemStore
的时间间隔。
一般建议调大,比如
10
小时,因为很多场景下
1
小时
Flush
一次会产生很多小文件,一方
面导致
Flush
比较频繁,另一方面导致小文件很多,影响随机读性能
数据更新超过一定阈值
如果
HBase
的某个
Region
更新的很频繁,而且既没有达到自动刷写阀值,也没有达到内存
的使用限制,但是内存中的更新数量已经足够多,
比如超过
hbase.regionserver.flush.per.changes
参数配置,默认为
30000000
,那么也是会
触发刷写的。
手动触发刷写
Shell
中通过执行
flush
命令
特别注意
:
以上所有条件触发的刷写操作最后都会检查对应的
HStore
包含的
StoreFiles
文件数是否超过
hbase.hstore.blockingStoreFiles
参数配置的个数,默认值是
16
。
如果满足这个条件,那么当前刷写会被推迟到
hbase.hstore.blockingWaitTime
参数设置的时
间后再刷写。
在阻塞刷写的同时,
HBase
还会请求
Compaction
或者
Split
操作。
5.2.
刷写策略
HBASE1.1
之前:
MemStore
刷写是
Region
级别的。就是说,如果要刷写某个
MemStore
,
MemStore
所在
的
Region
中其他
MemStore
也是会被一起刷写的
HBASE2.x
之后
FlushAllStoresPolicy
每次刷写都是对
Region
里面所有的
MemStore
进行的
FlushAllLargeStoresPolicy
判断
Region
中每个
MemStore
的使用内存是否大于某个阀值
,
大于这个阀值的
MemStore
将会被刷写。
flushSizeLowerBound = max((long)128 / 3, 16) = 42
FlushNonSloppyStoresFirstPolicy
5.3.
刷写流程
prepareFlush
阶段:
刷写的第一步是对
MemStore
做
snapshot
,
为了防止刷写过程中更新的数据同时在
snapshot
和
MemStore
中而造成后续处理的困难,
所以在刷写期间需要持有
updateLock
。持有了
updateLock
之后,这将阻塞客户端的写操
作。
所以只在创建
snapshot
期间持有
updateLock
,
max(32, hbase_heapsize * hbase.regionserver.global.memstore.size * 2 /
logRollSize)
而且
snapshot
的创建非常快,所以此锁期间对客户的影响一般非常小。
对
MemStore
做
snapshot
是
internalPrepareFlushCache
里面进行的。
flushCache
阶段:
如果创建快照没问题,
那么返回的
result.result
将为
null
。
这时候我们就可以进行下一步
internalFlushCacheAndCommit
。
其实
internalFlushCacheAndCommit
里面包含两个步骤:
flushCache
和
commit
阶段。
flushCache
阶段:
其实就是将
prepareFlush
阶段创建好的快照写到临时文件里面,
临时文件是存放在对应
Region
文件夹下面的
.tmp
目录里面。
commit
阶段:
将
flushCache
阶段生产的临时文件移到(
rename
)对应的列族目录下面,
并做一些清理工作,比如删除第一步生成的
snapshot
。
六、数据合并

6.1.
合并分类
HBase
根据合并规模将
Compaction
分为了两类:
MinorCompaction
和
MajorCompaction
Minor Compaction
是指选取一些小的、相邻的
StoreFile
将他们合并成一个更大的
StoreFile
,在这个过程中不会
处理已经
Deleted
或
Expired
的
Cell
但是会处理超过
TTL
的数据
一次
Minor Compaction
的结果是让小的
storefile
变的更少并且产生更大的
StoreFile
。
Major Compaction
是指将所有的
StoreFile
合并成一个
StoreFile
清理三类无意义数据:被删除的数据、
TTL
过期数据、版本号超过设定版本号的数据。
一般情况下,
Major Compaction
时间会持续比较长,整个过程会消耗大量系统资源,对上层
业务有比较大的影响。因此线上业务都会将关闭自动触发
Major Compaction
功能,改为手动
在业务低峰期触发。
6.2.
合并时机
触发
compaction
的方式有三种:
Memstore
刷盘、后台线程周期性检查、手动触发。
Memstore
刷盘
memstore flush
会产生
HFile
文件,文件越来越多就需要
compact
。
每次执行完
Flush
操作之后,都会对当前
Store
中的文件数进行判断,一旦文件数大于配置,就
会触发
compaction
。
compaction
都是以
Store
为单位进行的,而在
Flush
触发条件下,整个
Region
的所有
Store
都
会执行
compact
后台线程周期性检查
后台线程定期触发检查是否需要执行
compaction
,检查周期可配置。
hbase.server.thread.wakefrequency(
默认
10000
毫秒)
*hbase.server.compactchecker.interval.multiplier(
默认
1000)
CompactionChecker
大概是
2hrs 46mins 40sec
执行一次
线程先检查文件数是否大于配置,一旦大于就会触发
compaction
。
如果不满足,它会接着检查是否满足
major compaction
条件,
如果当前
store
中
hfile
的最早更新时间早于某个值
mcTime
,
就会触发
major compaction
(默认
7
天触发一次,可配置手动触发)。
手动触发
一般来讲,手动触发
compaction
通常是为了执行
major compaction
,一般有这些情况需要手
动触发合并
是因为很多业务担心自动
major compaction
影响读写性能,因此会选择低峰期手动触
发;
也有可能是用户在执行完
alter
操作之后希望立刻生效,执行手动触发
major
compaction
;
是
HBase
管理员发现硬盘容量不够的情况下手动触发
major compaction
删除大量过期数
据;
6.3.
合并策略
承载了大量
IO
请求但是文件很小的
HFile
,
compaction
本身不会消耗太多
IO
,而且合并完成之后对读的
性能会有显著提升。
线程池选择
HBase CompacSplitThread
类内部对于
Split
、
Compaction
等操作专门维护了各自所使用的
线程池
和
Compaction
相关的是如下的
longCompactions
和
shortCompactions
前者用来处理大规模
compaction
,后者处理小规模
compaction
默认值为
2 * maxFlilesToCompact * hbase.hregion.memstore.flush.size
如果
flush size
大小是
128M
,该参数默认值就是
2 * 10 * 128M = 2.5G
合并策略选择
HBase
主要有两种
minor
策略
: RatioBasedCompactionPolicy (0.96.x
之前
)
和
ExploringCompactionPolicy(
当前默认
)
RatioBasedCompactionPolicy(
基于比列的合并策略
)
ExploringCompactionPolicy策略(默认策略)


执行文件合并
分别读出待合并
hfile
文件的
KV
,并顺序写到位于
./tmp
目录下的临时文件中
将临时文件移动到对应
region
的数据目录
将
compaction
的输入文件路径和输出文件路径封装为
KV
写入
WAL
日志,并打上
compaction
标记,最后强制执行
sync
将对应
region
数据目录下的
compaction
输入文件全部删除
七、
数据切分
(Region Split)
通过切分,一个
region
变为两个近似相同大小的子
region
,再通过
balance
机制均衡到不同
region
server
上,使系统资源使用更加均衡。
7.1.
切分原因
数据分布不均匀。
同一
region server
上数据文件越来越大,读请求也会越来越多。一旦所有的请求都落在同一
个
region server
上,尤其是很多热点数据,必然会导致很严重的性能问题。
compaction
性能损耗严重。
compaction
本质上是一个排序合并的操作,合并操作需要占用大量内存,因此文件越大,占
用内存越多
compaction
有可能需要迁移远程数据到本地进行处理(
balance
之后的
compaction
就会存在
这样的场景),如果需要迁移的数据是大文件的话,带宽资源就会损耗严重。
资源耗费严重
HBase
的数据写入量也是很惊人的,每天都可能有上亿条的数据写入
不做切分的话一个热点
region
的新增数据量就有可能几十
G
,用不了多长时间大量读请求就会
把单台
region server
的资源耗光。
7.2.
触发时机
每次数据合并之后都会针对相应
region
生成一个
requestSplit
请求,
requestSplit
首先会执行
checkSplit
,检测
file size
是否达到阈值,如果超过阈值,就进行切分。
检查阈值算法主要有两种:
ConstantSizeRegionSplitPolicy
(
0.94
版本)和
IncreasingToUpperBoundRegionSplitPolicy
(当前)
ConstantSizeRegionSplitPolicy :
系统会遍历
region
所有
store
的文件大小,如果有文件大小
> hbase.hregion.max.filesize(
默
认
10G)
,就会触发切分操作。
IncreasingToUpperBoundRegionSplitPolicy
:
如果
store
大小大于一个变化的阀值就允许
split
。
默认只有
1
个
region
,那么逻辑这个
region
的
store
大小超过
1 * 1 * 1 * flushsize * 2 =
128M * 2 =256M
时,才会允许
split
切分之后会有两个
region
,其中一个
region
中的某个
store
大小大于
2 * 2 * 2 * flushsize * 2
= 2048M
时,则允许
split
后续超过
hbase.hregion.max.filesize + hbase.hregion.max.filesize *
随机小数
*
hbase.hregion.max.filesize.jitter
才允许
split
基本也就固定了,如果粗劣的计算可以把这个
hbase.hregion.max.filesize
的大小作为最后的
阀值,默认是
10G
7.3.
切分流程
寻找切分点
将一个
region
切分为两个近似大小的子
region
,首先要确定切分点。切分操作是基于
region
执
行的,每个
region
有多个
store
(对应多个
column famliy
)。系统首先会遍历所有
store
,找
到其中最大的一个,再在这个
store
中找出最大的
HFile
,定位这个文件中心位置对应的
rowkey
,作为
region
的切分点。
开启切分事务
切分线程会初始化一个
SplitTransaction
对象,从字面上就可以看出来
split
流程是一个类似
‘
事
务
’
的过程,整个过程分为三个阶段:
prepare - execute - rollback
prepare
阶段
在内存中初始化两个子
region
,具体是生成两个
HRegionInfo
对象,包含
tableName
、
regionName
、
startkey
、
endkey
等。同时会生成一个
transaction journal
,这个对象
用来记录切分的进展
execute
阶段
region server
更改
ZK
节点
/region-in-transition
中该
region
的状态为
SPLITING
。
master
检测到
region
状态改变。
region
在存储目录下新建临时文件夹
.split
保存
split
后的
daughter region
信息。
parent region
关闭数据写入并触发
flush
操作,将写入
region
的数据全部持久化到磁盘。
在
.split
文件夹下新建两个子文件夹,称之为
daughter A
、
daughter B
,并在文件夹中生
成引用文件,分别指向父
region
中对应文件。
将
daughter A
、
daughter B
拷贝到
HBase
根目录下,形成两个新的
region
。
parent region
通知修改
hbase.meta
表后下线,不再提供服务。
开启
daughter A
、
daughter B
两个子
region
。
通知修改
hbase.meta
表,正式对外提供服务。
rollback
阶段
如果
execute
阶段出现异常,则执行
rollback
操作。
为了实现回滚,整个切分过程被分为很多子阶段,回滚程序会根据当前进展到哪个子阶
段清理对应的垃圾数据。
7.4.
切分优化
对于预估数据量较大的表,需要在创建表的时候根据
rowkey
执行
region
的预分配。
通过
region
预分配,数据会被均衡到多台机器上,这样可以一定程度解决热点应用数据量剧增导致
的性能问题