【非关系型分布式数据库】HBase从入门到面试学习总结
国科大学习生活(期末复习资料、课程大作业解析、大厂实习经验心得等): 文章专栏(点击跳转)
大数据开发学习文档(分布式文件系统的实现,大数据生态圈学习文档等): 文章专栏(点击跳转)
HBase从入门到面试学习总结
- 一、 HBase概述
- 1.1 什么是HBase ?
- 1.2 HBase 数据模型
- 1.2.1 HBase 逻辑结构
- 1.2.2 HBase 物理存储结构
- 1.2.3 数据模型详解
- 1.3 HBase基本架构
- 二、HBase原理⭐
- 2.1 Master架构
- 2.2 RegionServer 架构
- 2.3 HBase写流程
- 2.4 MemStore Flush
- 2.5 HBase读流程
- 2.5.1 HFile 结构
- 2.5.2 HBase读流程
- 2.5.3 合并读取数据优化
- 2.6 StoreFile Compaction
- 2.7 Region Split
- 2.7.1 预分区(自定义分区)
- 2.7.2 系统分区
- 三、HBase优化
- 3.1 RowKey设计优化
- 3.2 HBase 使用经验法则
- 参考文献
- 项目地址
一、 HBase概述
1.1 什么是HBase ?
Apache HBase是一个开源的非关系型分布式数据库(NoSQL),它建立在Apache Hadoop之上,特别是Hadoop的HDFS(Hadoop Distributed File System)和Hadoop MapReduce计算框架。
HBase设计用于存储大规模的稀疏数据集,它提供了对数据的随机实时读/写访问,并通过行键(RowKey)进行索引。
1.2 HBase 数据模型
HBase 的设计理念依据Google的BigTable论文,论文中对于数据模型的首句介绍为:Bigtable 是一个稀疏的、分布式的、持久的多维排序map。
之后对于map的解释如下:该map由行键、列键和时间戳索引;map映射中的每个值(Value)都是一个未解释的字节数组。
1.2.1 HBase 逻辑结构
HBase 可以用于存储多种结构的数据,以JSON为例,存储的数据原貌为:
{
"row_key1":{
"personal_info":{
"name":"zhangsan",
"city":"北京",
"phone":"131********"
},
"office_info":{
"tel":"010-1111111",
"address":"atguigu"
}
},
"row_key2":{
"personal_info":{
"city":"上海",
"phone":"132********"
},
"office_info":{
"tel":"010-1111111"
}
},
……
}
在HBase里边,定位一行数据会有一个唯一的值,这个叫做行键(RowKey)。而在HBase的列不是我们在关系型数据库所想象中的列。HBase的列(Column)都得归属到列族(Column Family)中。在HBase中用列修饰符(Column Qualifier)来标识每个列。
在HBase里边,先有列族,后有列。
1.2.2 HBase 物理存储结构
物理存储结构即为数据映射关系,而在上面概念视图的空单元格,底层实际根本不存储。
1.2.3 数据模型详解
- NameSpace:命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表(Table)。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。建表时,只需指明列族,而列限定符无需预先定义。
- TimeStamp:用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
- Region:HBase中数据的水平切片,每个Region包含表中一定范围的行。当 Region 增长到一定大小后,会进行分裂(split),以保持系统的可扩展性。每个 Region 由一个RegionServer管理。
- Cell:数据存储的最小单元。由
{rowkey, column Family:column Qualifier, timestamp}
唯一确定的单元,在 HBase 中,数据是按照 Cell 来组织的(数据的读写操作都是基于 Cell 的。例如,当你读取一行数据时,你实际上是在读取该行中所有Cell的值。同样,当你写入数据时,你也是在写入一个或多个Cell)。Cell中的数据全部是字节码形式存贮。
1.3 HBase基本架构
- HMaster
- HBase 的主服务器,负责管理集群元数据和协调集群操作,如表的创建、删除、区域分配(region assignment)和负载均衡。
- HMaster 监控集群状态,处理 RegionServer 的上线和下线,并负责表的元数据管理。(注意,读写数据的请求无需HMaster参与)。
- RegionServer
- 负责处理客户端对数据的所有读写请求,例如写入数据put,查询数据get等
- 管理表的物理存储,包括数据文件和索引文件。
- 执行数据的存储和检索操作(即读写),以及数据的压缩、分裂和合并等维护任务(由HMaster监控,由RegionServer执行)。
- Zookeeper
- HBase 通过Zookeeper 来做 master 的高可用、记录RegionServer的部署信息、并且存储meta表的位置信息。HBase对于数据的读写操作时直接访问Zookeeper。
- HDFS
- HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高容错的支持。
二、HBase原理⭐
2.1 Master架构
- Meta表格:全称为hbase:meta,只是在list命令中被过滤掉了,本质上和HBase的其他表格一样。
- RowKey: (
[table],[region start key],[region id]
) 即表名,region起始位置和regionID。 - 列: info:regioninfo 为 region 信息,存储一个HRegionInfo 对象。 info:server 当前 region 所处的RegionServer 信息,包含端口号。info:serverstartcode 当前 region 被分到 RegionServer 的起始时间。
注意:
在客户端对元数据进行操作的时候才会连接master,如果仅仅是对数据进行读写,直接连接zookeeper 读取目录/hbase/meta-region-server 节点信息,会记录meta表格的位置。直接读取即可,不需要访问master,这样可以减轻master的压力,相当于master专注meta表的写操作,客户端可直接读取meta表。
2.2 RegionServer 架构
架构图详解:
- MemStore
写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile,写入到对应的文件夹store中。 - WAL
由于数据要经MemStore 排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中(预写日志),然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。 - BlockCache
读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。
2.3 HBase写流程
具体流程:
- 首先访问Zookeeper,获取hbase:meta表位于哪个RegionServer;
- 访问对应的RegionServer,获取hbase:meta表并将其缓存到内存中,作为连接属性MetaCache,由于表格具有一定的数据量,所以导致此链接较慢。
- 之后使用创建的连接获取Table(一个轻量级的连接),只有在第一次创建的时候会检查表格是否存在而访问RegionServer,之后再获取Table时不会访问RegionServer;
- 调用Table的put方法写入数据,此时还需要解析RowKey,对照缓存的MetaCache,查看具体写入的位置有哪个RegionServer;
- 将数据顺序写入(追加)到WAL(预写日志),此处写入是直接落盘的,并设置专门的线程控制WAL预写日志的滚动(类似Flume);
- 根据写入命令的RowKey和ColumnFamily查看具体写入到哪个MemStore,并且在MemStore中排序;
- 向客户端发送ack;
- 等达到MemStore的刷写时机后,将数据刷写到对应的StoreFile中。
2.4 MemStore Flush
MemStore 刷写由多个线程控制,条件互相独立(同时生效):
主要的刷写规则是控制刷写文件的大小,在每一个刷写线程中都会进行监控.
-
当某个memstroe的大小达到了
hbase.hregion.memstore.flush.size
(默认值 128M), 其所在region的所有memstore都会刷写。
当memstore 的大小达到了hbase.hregion.memstore.flush.size(默认值 128M) * hbase.hregion.memstore.block.multiplier(默认值 4)
时,会刷写同时阻止继续往该memstore写数据(防止数据丢失)。 -
由 HRegionServer 中的属性 MemStoreFlusher 内部线程 FlushHandler 控制。标准为 LOWER_MARK(低水位线)和HIGH_MARK(高水位线),意义在于避免写缓存使用过多的内存造成OOM:
当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写数据。 -
为了避免数据过长时间处于内存之中,到达自动刷写的时间,也会触发 memstore flush。由 HRegionServer 的属性 PeriodicMemStoreFlusher 控制进行,由于重要性比较低,5min才会执行一次。
-
当WAL文件的数量超过hbase.regionserver.max.logs,region 会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.log 以下(该属性名已经废弃,现无需手动设置,最大值为32)。
2.5 HBase读流程
2.5.1 HFile 结构
在了解读流程之前,需要先知道读取的数据是什么样子的。
HFile 是存储在HDFS上面每一个Store文件夹下实际存储数据的文件。里面存储多种内容。包括数据本身(keyValue键值对)、元数据记录、文件信息、数据索引、元数据索引和一个固定长度的尾部信息(记录文件的修改情况)。每一个HFile还会维护一个布隆过滤器(就像是一个很大的Map,文件中每有一种key, 就在对应的位置标记,读取时可以大致判断要get的key是否存在HFile中)。
KeyValue 内容如下:
rowlength -----------→ key 的长度
row -----------------→ key 的值
columnfamilylength --→ 列族长度
columnfamily --------→ 列族
columnqualifier -----→ 列名
timestamp -----------→ 时间戳(默认系统时间)
keytype -------------→ Put
注:HFile 存储经过序列化,无法直接查看。
2.5.2 HBase读流程
- 首先依然是创建connection连接(方法同HBase写流程)
- 创建Table对象发送get请求。
- 优先访问Block Cache,查找是否之前读取过,并且可以读取HFile的索引信息和布隆过滤器。
- 不管读缓存(Block Cache)中是否已经有数据了(因为可能已经过期),都需要再次读取写缓存和 store 中的文件(至少读取三个位置的数据)。
- 最终将所有读取到的数据合并版本,按照Get的要求返回即可。
2.5.3 合并读取数据优化
优化原因:每次读取数据都至少需要读取三个位置,最后进行版本的合并。效率会非常低,所以系统需要对此优化:
- HFile 带有索引文件,读取对应RowKey数据会比较快。
- Block Cache 会缓存之前读取的内容和元数据信息,如果HFile 没有发生变化(记录在HFile 尾信息中),则不需要再次读取。
- 使用布隆过滤器能够快速过滤当前HFile不存在需要读取的RowKey,从而避免读取文件。(布隆过滤器使用HASH算法,不是绝对准确的,出错会造成多扫描一个文件,对读取数据结果没有影响)
2.6 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 文件大小最大值,高于这个数不会被合并。
小合并(Minor Compaction)机制为拉取整个store中的所有文件,做成一个集合。之后按照从旧到新的顺序遍历。 判断条件为:
- 过小合并,过大不合并;
文件大小 / hbase.hstore.compaction.ratio < (剩余文件大小和)
则参与压缩。所以把比值设置过大,如10,会最终合并为1个特别大的文件,相反设置为0.4,会最终产生4个storeFile。 不建议修改默认值;- 满足压缩条件的文件个数达不到个数要求(3 <= count <= 10)则不压缩。
2.7 Region Split
Region 切分分为两种,创建表格时候的预分区即自定义分区,同时系统默认还会启动一个切分规则,避免单个Region中的数据量太大。
2.7.1 预分区(自定义分区)
每一个region维护着startRow 与endRowKey,如果加入的数据符合某个region维护的 rowKey 范围,则该数据交给这个region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
2.7.2 系统分区
Region的拆分是由HRegionServer完成的,在操作之前需要通过ZK汇报master,修改对应的Meta表信息添加两列info:splitA和info:splitB信息。之后需要操作HDFS上面对应的文件,按照拆分后的Region范围进行标记区分,实际操作为创建文件引用,不会挪动数据。刚完成拆分的时候,两个Region都由原先的RegionServer管理。之后汇报给Master, 由Master将修改后的信息写入到Meta表中。等待下一次触发负载均衡机制,才会修改Region 的管理服务者,而数据要等到下一次压缩时,才会实际进行移动。
不管是否使用预分区,系统都会默认启动一套Region 拆分规则。不同版本的拆分规则有差别。Hbase 2.0 引入了新的split策略:
- 如果当前RegionServer上该表只有一个Region, 按照 2 * hbase.hregion.memstore.flush.size 分裂,
- 否则按照 hbase.hregion.max.filesize 分裂。
三、HBase优化
3.1 RowKey设计优化
一条数据的唯一标识就是RowKey,那么这条数据存储于哪个分区,取决于RowKey处于哪个一个预分区的区间内,设计rowkey的主要目的,就是让数据均匀的分布于所有的Region中,在一定程度上防止数据倾斜(即:热点问题)。那么如何解决热点问题(数据倾斜),一般考虑以下几种方式:
- Hash散列设计
使用一个Hash值作为RowKey的前缀,Hash可以将数据负载均衡的分配到集群中;如何实现Hash散列,方式有很多,比如:将某一个字段假如是host字段取Hash之后放在rowkey的前面,或者说通过MD5Hash之后取前几位放在Rowkey的前面。这种方式可以很好的将rowkey均衡的分布到region上面,不会导致单个region的压力增大。 - 时间戳TimeStamp
对于时间要求比较高的读来说,可以选择在Rowkey后面添加timestamp,比如如果我们根据时间去查询1分钟,1小时,1天内的数据,那么这个就可以考虑在Rowkey中加入timestamp。 - “翻转TimeStamp”
“翻转timstamp”并将tiamestamp拼接上去,可以有效的保证第一条数据是最新的,这样当要get最新的一条数据的时候,根据条件读取第一条数据即可。(此翻转非彼翻转:RowKey = Host + Item + Long.MAX_VALUE – timestamp)。 - Salting
Salting与加密无关,它指的是将随机数放在RowKey的起始处,salting给每一RowKey随机指定了一个前缀来让它与其它行键有着不同的顺序,以此解决热点问题。
3.2 HBase 使用经验法则
官方给出了权威的使用法则:
- Region 大小控制10-50G
- Cell大小不超过10M(性能对应小于100K的值有优化),如果使用mob(Medium sized Objects 一种特殊用法)则不超过50M。
- 1张表有1到3个列族,不要设计太多。最好就1个,如果使用多个尽量保证不 会同时读取多个列族。
- 1到2个列族的表格,设计50-100个Region。
- 列族名称要尽量短,不要去模仿RDBMS(关系型数据库)具有准确的名称和描述。
- 如果RowKey设计时间在最前面,会导致有大量的旧数据存储在不活跃的Region 中,使用的时候,仅仅会操作少数的活动Region,此时建议增加更多的Region个数。
- 如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存不会占用太多的内存。
参考文献
Apache HBase – Apache HBase® Home
HBase架构详解及读写流程-腾讯云开发者社区-腾讯云 (tencent.com)
尚硅谷HBase2.x教程来袭,从零出发,全面掌握
项目地址
BigDataDev: 大数据核心框架学习pro (gitee.com)
欢迎大家参考!
15点23分 2024年10月18日
非关系型分布式数据库 —— HBase内容学习整理,如有错误,欢迎评论区交流指出。
不积跬步无以至千里!