
1. 基本概念
Apache HBase(Hadoop DataBase)是一个开源的、高可靠性、高性能、面向列(这里指列族,非列式存储)、可伸缩、实时读写的分布式数据库。
1.1. 特点
易扩展(通过增加HRegionServer扩展,提升HBase上层处理能力;基于HDFS,提升存储能力)
容量大(单表可存十亿行、作用PB级别数据中)、
面向列(列族包含多个列,通常通过列族查找列族中的部分列可以提高效率)
多版本(默认五个,可以查看历史单元数据)
稀疏性(一行数据中可能存在部分列)
高可靠(又日志先行机制,日志数据先存入HLog中,有助于数据恢复,数据再存入BlockCashe内存空间,Hbase中还有zookeeper中的数据备份)
高性能:LSM树数据结构和RowKey有序排序,使得 HBase 写入性能非常高。HRegion 切分、主键索引、缓存机制使得 HBase 在海量数据下具备 一定的随机读取性能,该性能针对 RowKey 的查询能够到达毫秒级别。
1.2. 应用
适合处理十亿或者百亿的数据,否则工作的机器少,导致资源利用不充分。
存储业务数据:车辆 GPS 信息,司机点位信息,用户操作信息,设备访问信息。
存储日志数据:架构监控数据(登录日志,中间件访问日志,推送日志,短信邮件发送记录),业务操作日志信息。
存储业务附件:UDFS 系统(去中心化文件系统)存储图像,视频,文档等附件信息。
HBase和RDBMS的区别

2. 数据模型

在HBase中,一条数据一个唯一的RowKey,代表数据的Key,任意数量的列,列中的数据可以多版本,一个或多个列组成列族,同一个列族中的列数据存储再同一个HFile中。
定位数据的顺序:RowKey → Column Family → Column Qualifier → Version。
HBase 表中的数据是疏松地存储的,因此用户可以动态地为数据定义各种不同的列。比如有的RowKey当中有的列,有的RowKey当中可以没有。同时 HBase 会将表按主键划分为多个 HRegion 存储在不同的 HRegionServer 上。
2.1. NameSpace
命名空间类似数据库的概念,为逻辑分组,可对命名空间增删改查。
default:没有明确指定命名空间的表将自动落入此命名空间
hbase:系统命名空间,用于包含 HBase 的内部表和元数据表
2.2. Table
类似数据表的意思,由行和列组成。
2.3. RowKey
RowKey类似主键,不同的是他是一行数据唯一的标识,任意字符串(最大长度64k),每个HRegion中的RowKey通过字典序排序。
访问HBase的三种方式:
基于RowKey的单行查询、基于RowKey的范围查询,全表扫描查询(少用)
2.4. ColumnFamily
列族,同类的列放在一起,每个列族都哟一组存储属性。
- 是否应该缓存在内存中
- 数据如何被压缩或行键如何编码
创建表必须指定列,官方推荐列族数量最好<=3,过多不利于索引和管理。
2.5. ColumnQualifier
列名,列名可以更改,每列可能有不同的列标识,“列族:列”,列可以根据需求动态添加或删除,同一个表中的列数据可以不同。
2.6. Timestamp
多版本,相同 RowKey 的数据按照 Timestamp 倒序排列,默认查询最新的版本,可以指定版本或值来查询数据。可以作为存储单元的时间戳索引来操作值,时间戳也可以由客户显式赋值,如果应用程序要避免 数据版本冲突,就必须自己生成具有唯一性的时间戳。
版本回收方案:
保存数据最后的N个版本
保存最近一段时间的版本
2.7. Cell
数据单元,Cell 由 Row,Column Family,Column Qualifier,Version 组成。以字节码方式存储,因为HDFS数据就是字节数组。
3. 架构模型

3.1. zookeeper
主要作用:
选举HMaster,可以用户选择。
监控HRegionServer:HRegionServer返回心跳,判断运行情况。
维护元数据和集群配置:存放整个HBase集群的信息:
存储HRegin的入口,存储所有元数据信息。
存储HBase的Scheme,有哪些Table,每个Table有哪些列族。

3.2. Client
提供了访问HBase的接口,通过元数据表定位HRegionServer,找到数据的方式。
发送的请求分为,DDL,DML,DQL
3.3. HMaster
集群的主节点,通过zookeeper实现高可用(Active和Backup)切换
管理分配:管理和分配HRegion,负责启动的时候分配HRegion到HRegionServer当中,管理用户的DDL操作。
注意:表的元数据存储在zookeeper中,表的数据存储在HReginServer中(HDFS)中
负载均衡:将用户数据均衡的放在HRegionServer中,将请求也均分分布在HRegionServer上。
维护数据:发现失效的 HRegion,并将失效的 HRegion 分配到正常的 HRegionServer 上。当某个 HRegionServer 下线 时迁移其内部的 HRegion 到其他 HRegionServer 上。
权限控制

3.4. HRegionServer
对接用户的读写请求,Hbase数据的管理者
和HMaster保持心跳,汇报节点的情况(空闲的空间,是否有宕机)
创建表会分配一个HRegion对应一个表。
负责切分在运行过程中变得过大的 HRegion
当 HRegionServer 意外关闭的时候,当前节点的 HRegion 会被其他 HRegionServer 管理;
维护 HMaster 分配给它的 HRegion,处理对这些 HRegion 的 IO 请求;
WAL:Write Ahead Log 日志先行。记录了数据写入、更新日志,它被用来做故障恢复;
MemStore:写缓存,数据首先会被写入到 MemStore ,每个列族都有一个。
负责与底层的 HDFS 交互,存储数据(HLog、HFile)到 HDFS。
BlockCache:读缓存,在内存中存储了最常访问的数据,采用 LRU 机制进行淘汰。

当某个 HRegionServer 宕机后,ZooKeeper 会通知 HMaster 进行失效备援。下线的 HRegionServer 所负责的 HRegion 暂 时停止对外提供服务,HMaster 会将HRegionServer 所负责的 HRegion 转移到其他 HRegionServer 上,并且会对下线的
HRegionServer 进行日志重放,将 MemStore 中还未持久化到磁盘中的数据进行恢复。
3.5. HRegion

一个 HRegionServer 包含了多个 HRegion。HBase 将表中的数据基于 RowKey 的不同范围划分到不同 HRegion 上,每个 HRegion 都负责一定范围的数据存储和访问。
每个 表一开始只有一个 HRegion,当增大到指定阀值(10G)的时候,HRegion 就会
等分成两个 HRegion,切分后其中一个 HRegion 会被转移到其他的 HRegionServer 上,实现负载均衡。
3.6. Split
当一个 HRegion 达到一定的大小就会自动 Split 成两个 HRegion。并存在其他HRegionServer中。当一个 Table 刚被创建的时候,HBase 默认的分配一个 HRegion 给 Table。
3.7. Store
一个HRegion有多个Store,每个Store对应一个列族,包含一个MemStore和和多个StoreFile。
MemStore:内存中的空间,数据会先存到MemStore中,当数据达到(128m)时会溢写到磁盘中,每次溢出的数据都会时一个StoreFile,查找数据先找MemStore然后再找StoreFile。
StoreFile:有MemStore溢写产生,底层时Hfile格式。
Hfile:也就是StoreFile,HDFS中称Hfile,HBase称StoreFile。
3.8. HFile

Block:每个 HFile 由 N 个 Block 组成。
KeyValue:每个 Block 又是由多个 KeyValue 数据组成,KeyValue 对象是数据存储的核心,KeyValue 包装了一个字节 数组,同时将偏移量 offsets 和 lengths 放入数组中,这个数组指定从哪里开始解析数据内容。
3.9. HLog
日志文件,一个HRegionServer中只有一个HLog文件,可以对日志回放,故障恢复,例如磁盘掉电导致 MemStore 中的数据没有持久化存储到 StoreFile,这时就可以通过 HLog 日志重放来恢复数据。
3.10. BlockCache
blockCache备份了部分block的数据,保存着最近被访问的数据块,在CPU的缓存中。
客户端读取block会先在blockCache中查找,如果不存在就去HFile中查找。
Block 是 HBase 中最小的数据读取单元,即数据从 HFile 中读取都是以 Block 为最小单元执行的。
4. 交互方式
4.1. HBaseShell
4.1.1. 基本命令
[root@node01 ~]# hbase shell--登入
hbase:001:0> exit--退出
hbase> help--帮助
hbase> status--服务器状态
hbase> version--版本
hbase> processlist--当前任务列表
hbase> whoami --当前用户
4.1.2. namespace命令
命名空间类似于关系型数据库中的数据库的概念。
# 查看所有命令空间
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> describe_namespace 'ns1'
# 修改命名空间,添加属性
hbase> alter_namespace 'ns1', {METHOD => 'set', 'PROPERTY_NAME' => 'PROPERTY_VALUE'}
# 修改命名空间,移除属性
hbase> alter_namespace 'ns1', {METHOD => 'unset', NAME=>'PROPERTY_NAME'}
# 删除命名空间,命名空间必须为空
hbase> drop_namespace 'ns1'
4.1.3. DDL命令

# 在命名空间 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}
# 在 colfam1 列族上启用 ROWCOL Bloom 过滤器,此时的布隆过滤器为 Rowkey + 列
# 默认情况下启用基于行的 Bloom 过滤器(BLOOMFILTER => 'ROW'),可以通过 BLOOMFILTER => 'NONE' 禁用
# 如果经常扫描整行,那么行 + 列组合将不会提供任何好处
hbase> create 'mytable',{NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'}
#查看所有表
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> exists 't1'
#修改表
# 修改 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}
5. 读写流程
HBase 0.96 以后整个流程为: Client → ZooKeeper → hbase:meta → 用户的表的 HRegion 。

hbase:meta 表结构如下:

rowkey:表名,格式为 表名,起始键,HRegion的时间戳.Encode编码. ;
table:state:表的状态,启用还是禁用状态;
info:state:HRegion 的状态,正常情况下为 OPEN;
info:server:HRegionServer 的地址和端口,如 node03:16020;
info:serverstartcode:HRegionServer 启动的 13 位时间戳;
info:sn:server 和 serverstartcode 的组合,如 node03:16020,1662183040273;
info:seqnumDuringOpen:HRegion 在线时长的二进制串;
info:regioninfo:HRegion 的详细信息,如:ENCODED、NAME、STARTKEY、ENDKEY 等。
5.1. 读取数据流程
5.1.1. 数据组织

Client访问Zookeeper,获取hbase:meta所在的HRegionServer节点信息。
Client访问hbase:mata所在的HRegionServer,获取hbase:mate记录的元数据后加载到内存中,然后从内存中查询出RowKey所在的HRegion在哪个HRegionServer中。
Client对RowKey所在的HRegion对应的HRegioServer发起求数据。
HRegionServer构建RegionScanner,用于对HRegion的数据索引。
RegionScanner构建StoreScanner,HRegion中有多少个Store就有多少个StoreScanner,Store的数量取决于ColumnFamily数量,用于对该列族的数据检索。
所有的StoreScanner合并构建最小堆,StoreHeap:priorityQueue;
StoreScanner构建一个MemStoreScanner和一个或多个StoreFIleScanner(数量取决于StoreFIle的数量)
过滤掉能够确定所要查询的ROwKey一定不在的StoreFileScanner或MemStoreScanner。
经过赛选后留下的Scanner开始读取数据的准备,将对应的StoreFile定位到曼珠RowKey的起始位置。
将所有的StoreFileScanner和MemStoreScanner合并构建最小堆KeyValueHeap:priorityQueue,排序的规则按照KeyValue从大到小排序。
从KeyValueHeap:priorityQueue中经过一系列筛选后一行行得到所需查询的keyValue;
5.1.2. 查询过程
项目有 100 亿业务数据,占空间 10TB。存储在一个 HBase 集群上(由多个服务器数据节点构成),每个数据节点上有若干个 HRegion(区域),每个 HRegion 实际上就是 HBase 中一批数据的集合(一段连续范围 RowKey 的数据)。
第一步
通过主键RowKey查询,Client通过hbase:mate定位HRegionServer中的HRegion地址,所有记录被切分成5000个HRegion,每个HRegion大约2G。
由于记录在1个HRegion中,所以我们只需要查询这2G的HRegion,就可以找到对应的记录。
第二步
数据按照列族存储,比如有一部分列是和人员列族相关,一部分列和公司列族相关,一部分是和人员交易信息列族相关,其他列是和其他的一个列族相关。
也就是四个列,2G的HRegion中,分为4个列,我们只需要查找500M的一个列族,就可以找到对应的记录。
第三步
1个列族会包含>=1个HFile(StoreFile)。如果一个HFile的大小为100M,那么列族就包含5个HFile,由于RowKey是排好序存储在HRegion当中的,在HFile中的数据也是排好序的,我们可以从前面查询,也可以从后面查询,那么我们只需要遍历一半的HFile也就死2.5个HFile找到记录,也就是250M数据中查找。
第四步
HFile是通过键值对的方式存储,只需要遍历Key的值来查找,Key的长度相比Value要小很多,假如是1:24,那么最终只需要在10M数据量中查找记录。
6. 数据刷写
6.1. 触发时机
内存阈值
MemStore默认是128M超过就刷出,当数据增长快为128*4的时候,
内存总和
HRegionServer中的所有MemStore占用内存到阈值也会刷写,这时候当前 HRegionServer 的所有写操作将会被阻塞,这个阻塞可能会持续到分钟级别。
日志阈值
HBase使用WAL日志先行机制,日志也存储在内存中,为了避免日志文件因为HRegionServer宕机而数据丢失,达到一定数量也会刷入到磁盘中。
定期刷写
定时器达到阈值的时候刷写,一般调大,小文件较多,合并后可能还是小文件,再合并会导致时间加多。
更新频率
参数配置更新数量,默认达到 30000000 次,也会触发刷写。
手动刷写
hbase> flush 'TABLENAME'
hbase> flush 'REGIONNAME'
hbase> flush 'ENCODED_REGIONNAME'
hbase> flush 'REGION_SERVER_NAME'
6.2. 刷写策略
FlushAllLargeStoresPolicy:判断 HRegion 中每个 MemStore 的使用内存是否大于指定阀值,大于阀值的 MemStore 将会 被刷写。阈值计算公式: flushSizeLowerBound = max((long)128 / 3, 16) = 42 。
FlushNonSloppyStoresFirstPolicy:将 Region 中的 MemStore 按照isSloppyMemStore 分到两个 HashSet 里面 ( sloppyStores 和 regularStores )然后:
判断 regularStores 里面是否有 MemStore 内存占用大于相关阀值的 MemStore,有的话就会对这些 MemStore 进行刷写,其他的不做处理,这个阀值计算和 FlushAllLargeStoresPolicy 的阀值计算逻辑一致。
如果 regularStores 里面没有 MemStore 内存占用大于相关阀值的 MemStore,这时候就开始在 sloppyStores 里面寻找是否有 MemStore 内存占用大于相关阀值的 MemStore,有的话就会对这些 MemStore 进 行刷写,其他的不做处理。
如果上面 sloppyStores 和 regularStores 都没有满足条件的 MemStore 需要刷写,这时候就将 FlushNonSloppyStoresFirstPolicy 策略久退化成 FlushAllStoresPolicy 策略了。
6.3. 刷写流程
prepareFlush 阶段
flushCache 阶段
7. 数据合并
7.1. 合并分类
Minor Compaction(次要/小)
选取一些小的、相邻的 StoreFile 将他们合并成一个更大的 StoreFile,在这个过程中不做任何删除数据、多版本数据的清理工作,但是会对 minVersion=0 并且设置 TTL 的过期版本数据进行清理。
Major Compaction(主要/大)
将所有的 StoreFile 合并成一个 StoreFile 清理三类无意义数据:被删除的数据、TTL 过期数据、版本号超过设定版本号的数据。
总结
Minor Compaction:快速让小文件合并成大文件
Major Compaction:清理大文件不必要的数据,释放空间
7.2. 合并时机
MemStore刷盘
MemStore Flush 会产生 HFile 文件,文件越来越多就需要 Compact(压实)。每次执行完 Flush 操作之后,都会对当前 Store 中的文件数进行判断,一旦文件数大于配置,就会触发 Compaction。
周期性检查
后台线程定期触发检查是否需要执行 Compaction,检查周期可配置。文件数是否大于配置,一旦大于就会触发 Compaction。
手动执行
一般来讲,手动触发 Compaction 通常是为了执行 Major Compaction,一般有这些情况需要手动触发合并。
7.3. 合并策略

当前所剩候选文件数 <= 阈值(默认为 3)当前文件 2G < 所有文件大小总和 1G * 1.2,高峰期不合并,非高峰期合并;
8. 数据切分
9. 表设计
9.1. 行键设计
HBase 中的行默认按行键的字典序进行排序,这种设计优化了扫描(Scan),大量访问会使热点 HRegion 所在的单个机器超出自身承受能力,性能下降甚至 HRegion 不可用。这也会对同一台区域服务器HRegionServer)托管的其他区域(HRegion)产生 不利影响(主机资源全部被这个热点 HRegion 占用,已无法服务其他 HRegion 的请求)。
9.1.1. 策略
反转策略,将RowKey的值反转,比如手机号反转,避免1开头的所有手机号在同一个HRegion中。
比如:1301223232 -->2323221031
加盐策略:在Rowkey前添加数据数,这样在排序的时候会分配到不同的HRegion中。
比如:A-user-1、A-user-10、A-user-11
哈希策略:将数据RowKey通过Hash中的MD5的值改变,打散放入到不同的HRegion中。
例如: RowKey 为 1001 的,MD5 后变成:b8c37e33defde51cf91e1e03e51657da
反转、加盐、Hash 都属于散列思想,目的就是把 RowKey 打散,但是又有迹可循
9.1.2. 预分区
HRegion 有两个非常重要的属性:StartKey 与 EndKey,表示这个 HRegion 维护的 RowKey 范围,当我们要读/写数据时,如果 RowKey 落在某个 StartKey - EndKey 范围 内,那么就会定位到目标 HRegion 并且读/写到相关的数据。
9.1.3. 三原则
唯一原则:RowKey作为主键要是唯一值,也可以通过组合键来作为RowKey,例如: 手机号_时间戳
长度原则:RowKey是二进制码流,长度为64kb,为了减少查找时间RowKey的长度应该越短越好,有些情况下需要对其RowKey的长度,比如 ID 取 12 位,9 位 ID 前就需要补齐 3 个 0,否则就会出现 123456789 比 654321 排在前面的问题。对齐长度后, 000000654321 就会排在 000123456789 之前,符合预期。
散列原则: 如果 RowKey 是按时间戳的方式递增,不要将时间放在二进制码的前面。建议将 RowKey 的高位作为散列字段,低位放时间字段,这样将提高数据均衡分布在每个 HRegionServer 实现负载均衡的几率。例如:1000020220901100250135、 2000020220901122335367、3000020220901155117621。
总结
HBase 的 RowKey 设计需要遵循以下原则:
唯一原则:
单主键
组合主键(注意顺序)
长度原则:
不要超过 16 个字节
对齐 RowKey 长度
散列原则:
反转
加盐
Hash
9.2. 列族设计
追求原则:尽量少列族。
最优设计:将互相关系大的keyvalue放在同一个列族,帮助提高查询效率。
控制长度:列族的长度小,节省空间,提高效率。


被折叠的 条评论
为什么被折叠?



