让你彻底学会HBase
让你彻底学会HBase
Apache HBase(Hadoop DataBase)是一个开源的、高可靠性、高性能、面向列(这里指列族,非列式存储)、可伸缩、实时读写的分布式数据库。利用 Hadoop HDFS 作为其文件存储系统,利用 ZooKeeper 作为其分布式协同服务。主要用来存储非结构化和半结构化的松散数据(列式存储 NoSQL 数据库)。
注意:HBase 是列族数据库(Column-Family Database),不是列式数据库(Column-Oriented Database)。
总结:HBase 是运行在 HDFS 之上的面向列(列族)的数据库管理系统。
特点
易扩展
容量大
面向列
多版本
稀疏性
高可靠
高性能
HBase 和 RDBMS 的区别
数据类型
在 HBase 表中,一条数据拥有一个全局唯一的主键(RowKey)和任意数量的列(Column Qualifier),每个列的数据存储支持多个版本(Version),一列或多列组成一个列族(Column Family),同一个列族中列的数据在物理上都存储在同一个 HFile 中。这样基于列存储的数据结构有利于数据缓存和查询。
在 HBase 中定位一条数据需要通过:RowKey → Column Family → Column Qualifier → Version。
HBase 表中的数据是疏松地存储的,因此用户可以动态地为数据定义各种不同的列。
HBase 中的数据按主键排序(字典序),同时HBase 会将表按主键划分为多个 HRegion 存储在不同的 HRegionServer 上,以完成数据的分布式存储和读取。
NameSpace
命名空间类似于关系型数据库中的数据库的概念,他其实是表的逻辑分组。
default:没有明确指定命名空间的表将自动落入此命名空间
hbase:系统命名空间,用于包含 HBase 的内部表和元数据表
Table
Table 和关系型数据库中的表一个意思,由行和列组成。
RowKey
RowKey 的概念与关系型数据库中的主键相似,是一行数据的唯一标识。RowKey 可以是任意字符串(最大长度是64KB,实际应用中长度一般为 10-100 Bytes),RowKey 以字节数组保存。存储数据时,数据会按照 RowKey 的字典序排序存储,所以设计 RowKey 时,要充分利用排序存储这个特性,将经常一起读取的行存放到一起。
访问 HBase 数据的方式有三种:
- 基于 RowKey 的单行查询;
- 基于 RowKey 的范围查询;
- 全表扫描查询。
RowKey 如何设计,设计不好会产⽣什么后果
唯⼀原则:
单主键
组合主键(注意顺序)
⻓度原则:
不要超过 16 个字节
对⻬ RowKey ⻓度
Rowkey是⼀个⼆进制码流,Rowkey的⻓度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。
不要设计太⻓的原因如下:
1)数据的持久化⽂件HFile中是按照Key Value 存储的,如果Rowkey过⻓⽐如100个字节,1000万列数据,光Rowkey就要占⽤100*1000 万=10亿个字节,将近1G数据,这会极⼤影响 HFile的存储效率;
2)MemStore将缓存部分数据到内存,如果 Rowkey字段过⻓内存的有效利⽤率会降低,系统将⽆法缓存更多的数据,这会降低检索效率。因此Rowkey的字节⻓度越短越好;
3)⽬前操作系统是都是64位系统,内存8字节对⻬。控制在16个字节,8字节的整数倍利⽤操作系统的最佳特性。
散列原则:
反转
加盐
Hash
设计不好会出现热点问题:
HBase 中的⾏默认按⾏键的字典序进⾏排序,这种设计优化了扫描(Scan),允许将相关的⾏或彼此靠近的⾏⼀起读取。但是,设计不佳的⾏键是 Hot-Spotting 的常⻅来源。
热点发⽣在⼤量 Client 直接访问集群的⼀个或极少数个节点,⼤量访问会使热点 HRegion 所在的单个机器超出⾃身承受能⼒,性能下降甚⾄ HRegion 不可⽤。这也会对同⼀台区域服务器(HRegionServer)托管的其他区域(HRegion)产⽣不利影响(主机资源全部被这个热点HRegion 占⽤,已⽆法服务其他 HRegion 的请求)。
Column Family
Column Family 即列族,HBase 基于列划分数据的物理存储,同一个列族中列的数据在物理上都存储在同一个 HFile 中。一个列族可以包含任意多列,一般同一类的列会放在一个列族中,每个列族都有一组存储属性:
是否应该缓存在内存中;
数据如何被压缩或行键如何编码等。
HBase 在创建表的时候就必须指定列族。HBase 的列族不是越多越好,官方推荐一个表的列族数量最好小于或者等于三,过多的列族不利于 HBase 数据的管理和索引。
为什么不建议 HBase 设计过多列族
- 内存开销: 列族过多,内存开销会逐渐积累,导致RegionServer的内存占⽤增加,影响集群的稳定性
- 磁盘开销: 过多的列族会导致更多的磁盘寻址和IO操作,从⽽增加了磁盘开销
- 查询性能下降: 查询跨越多个列族,性能会受影响
- 写⼊性能下降:写⼊操作需要锁定列族,列族过多会导致性能下降
Column Qualifier
列族的限定词,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识。使用的时候必须 列族:列 ,列可以根据需求动态添加或者删除,同一个表中不同行的数据列都可以不同。
Timestamp
Timestamp 是实现 HBase 多版本的关键。在 HBase 中,使用不同的 Timestamp 来标识相同 RowKey 对应的不同版本的数据。相同RowKey 的数据按照 Timestamp 倒序排列,默认查询的是最新的版本,当然用户也可以指定 Timestamp 的值来读取指定版本的数据。
HBase 通过 RowKey 和 Column Family,Column Qualifier 来确定一个存贮单元,然后再通过时间戳来进行索引。时间戳的类型是 64 位整型,时间戳默认是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值,如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。
为了避免数据存在过多版本而造成管理(包括存贮和索引)负担,HBase 提供了两种数据版本回收方案:
- 保存数据的最后 n 个版本
- 保存最近一段时间内的版本(比如最近七天)
Cell
Cell 由 Row,Column Family,Column Qualifier,Version 组成。Cell 中的数据是没有类型的,全部使用字节码形式存贮,因为 HDFS 上的数据都是字节数组。
架构模型
HBase 可以将数据存储在本地文件系统,也可以存储在 HDFS 文件系统。在生产环境中,HBase 一般运行在 HDFS 上,以 HDFS 作为基础的存储设施。用户通过 HBase Client 提供的 Shell 或 Java API 来访问 HBase 数据库,以完成数据的写入和读取。HBase 集群主要由 HMaster、HRegionServer 和 ZooKeeper 组成。
ZooKeeper
HBase 通过 ZooKeeper 来完成选举 HMaster、监控 HRegionServer、维护元数据集群配置等工作。
选举 HMaster
监控 HRegionServer(节点探活)
维护元数据和集群配置
存储所有 HRegion 的寻址入口(META 元数据表),存储所有的的元数据信息;
存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family。
Client
HBase Client 为用户提供了访问 HBase 的接口,可以通过元数据表(客户端负责发送请求到数据库)来定位到目标数据的HRegionServer。
- HBase Shell
- Java API
HMaster
HMaster 是 HBase 集群的主节点,负责整个集群的管理工作,HMaster 可以实现高可用(Active 和 Backup),通过 ZooKeeper 来维护主备节点的切换。
管理分配:管理和分配 HRegion,负责启动的时候分配 HRegion 到具体的 HRegionServer,又或者在分割 HRegion 时关于新 HRegion 的分配。管理用户对 Table 结构的 DDL(创建,删除,修改)操作。
- 表的元数据信息存储在 ZooKeeper
- 表的数据存储在 HRegionServer 上(实际存储在 HDFS 上)
负载均衡:一方面负责将用户的数据均衡地分布在各个 HRegionServer 上,防止 HRegionServer 数据倾斜过载。另一方面负责将用户的请求均衡地分布在各个 HRegionServer 上,防止 HRegionServer 请求过热;
维护数据:发现失效的 HRegion,并将失效的 HRegion 分配到正常的 HRegionServer 上。当某个 HRegionServer 下线时迁移其内部的HRegion 到其他 HRegionServer 上。
权限控制。
HRegionServer
HRegionServer 直接对接用户的读写请求,是真正干活的节点,属于 HBase 具体数据的管理者。
- 实时和 HMaster 保持心跳,汇报当前节点的信息;
- 当接收到 HMaster 的命令创建表时,会分配一个 HRegion 对应一张表;
- 负责切分在运行过程中变得过大的 HRegion;
- 当 HRegionServer 意外关闭的时候,当前节点的 HRegion 会被其他 HRegionServer 管理;
- 维护 HMaster 分配给它的 HRegion,处理对这些 HRegion 的 IO 请求;
当某个 HRegionServer 宕机后,ZooKeeper 会通知 HMaster 进行失效备援。下线的 HRegionServer 所负责的 HRegion 暂时停止对外提供服务,HMaster 会将该 HRegionServer 所负责的 HRegion 转移到其他 HRegionServer 上,并且会对下线的 HRegionServer 进行日志重放,将MemStore 中还未持久化到磁盘中的数据进行恢复。
HRegion
一个 HRegionServer 包含了多个 HRegion。HBase 将表中的数据基于 RowKey 的不同范围划分到不同 HRegion 上,每个 HRegion 都负责一定范围的数据存储和访问。
HRegion 是 HBase 中分布式存储和负载均衡的最小单元,不同的 HRegion 可以分布在不同的 HRegionServer 上。
HRegion 到阀值(10G)的时候,就会等分成两个 HRegion,切分后其中一个HRegion 会被转移到其他的 HRegionServer 上,实现负载均衡。可以根据自己的业务进行预分区。
Split
当一个 Table 刚被创建的时候,HBase 默认的分配一个 HRegion 给 Table。可以用 pre-splitting 在创建 Table 时提前生成多个 HRegion。
HBase 在每次数据合并之后都会针对相应 HRegion 生成一个 requestSplit 请求,requestSplit 首先会执行 checkSplit,检测 FileSize 是否达到阈值,如果超过阈值,就进行切分。
在 0.94 版本之前 ConstantSizeRegionSplitPolicy 是默认和唯一的 Split 策略。当某个 Store(对应一个 Column Family)的大小大于配置值hbase.hregion.max.filesize 的时候(默认 10G)HRegion 就会自动分裂。
而 0.94 版本之后 IncreasingToUpperBoundRegionSplitPolicy 是默认的 Split 策略。这个策略中,最小的分裂大小和 Table 的某个HRegionServer 的 HRegion 个数有关,当 StoreFile 的大小大于以下公式得出的值的时候就会 Split。
# R 为同一个 Table 中在同一个 HRegionServer 中的 HRegion 的个数
Min(R^2 * "hbase.hregion.memstore.flush.size", "hbase.hregion.max.filesize")
Store
一个 HRegion 由多个 Store 组成,每个 Store 都对应一个 Column Family,Store 包含 1 个 MemStore 和 0 或多个 StoreFile 组成。
MemStore:作为 HBase 的内存数据存储,数据的写操作会先写到 MemStore 中,当 MemStore 中的数据增长到指定阈值(默认 128M)后,HRegionServer 会启动 FlushCache 进程将 MemStore 中的数据写入 StoreFile 持久化存储,每次写入后都形成一个单独的 StoreFile。当客户端检索数据时,先在 MemStore 中查找,如果 MemStore 中不存在,则会在 StoreFile 中继续查找。
StoreFile:MemStore 中的数据写到文件后就是 StoreFile,StoreFile 底层是以 HFile 格式保存的。HBase 以 StoreFile 的大小来判断是否需要切分 HRegion。当一个 HRegion 中所有 StoreFile 的大小和数量都增长到超过指定阈值时,HMaster 会把当前 HRegion 分割为两个,切分后其中一个 HRegion 会被转移到其他的HRegionServer 上,实现负载均衡。
HFile:HFile 和 StoreFile 是同一个文件,只不过站在 HDFS 的角度称这个文件为 HFile,站在 HBase 的角度就称这个文件为 StoreFile。是HBase 在 HDFS 中存储数据的格式,它包含多层的索引,这样在 HBase 检索数据的时候就不用完全的加载整个文件。
HFile
StoreFile(HFile) 是 HBase 最终存储数据的介质.
Block:每个 HFile 由 N 个 Block 组成。
KeyValue:每个 Block 又是由多个 KeyValue 数据组成,KeyValue 对象是数据存储的核心,KeyValue 包装了一个字节数组,同时将偏移量offsets 和 lengths 放入数组中,这个数组指定从哪里开始解析数据内容。
KeyValue 对象不跨 Block 存储,假如这里有一个 KeyValue 的大小为 8M,即使 Block-Size=64KB,当读取该 KeyValue 的时候也是以一个连贯的 Block 进行读取。
无论是 Data Block Index 还是 Bloom Filter,都采用了分层索引的设计。
HLog
一个 HRegionServer 只有一个 HLog 文件。负责记录数据的操作日志,当 HBase 出现故障时可以进行日志重放、故障恢复。例如磁盘掉电导致 MemStore 中的数据没有持久化存储到 StoreFile,这时就可以通过 HLog 日志重放来恢复数据。
HLogKey 中记录了写入数据的归属信息,除了 Table 和 HRegion 名称外,同时还包括 Sequence Number 和 Timestamp:
Timestamp:写入时间。
Sequence Number:起始值为 0,或者是最近一次存入文件系统中的 Sequence Number。
读写流程
三层索引
HBase 0.96 以前
HBase 0.96 以前内部维护了两张特殊的表: -ROOT- 表和 .META. 表,用来查找各种表的 HRegion 位置。这两张特殊的表也像 HBase 中的其他表一样会切分成多个 HRegion。 -ROOT- 表比 .META. 更特殊一些,永远不会切分超过一个 HRegion,这样保证了只需要三次跳转,就能定位到任意 HRegion。
-ROOT- :记录 .META. 表的 HRegion 信息。
.META. :记录用户的表的 HRegion 信息。
而 -ROOT- 表的 HRegion 位置信息存放在 ZooKeeper 中,通过 ZooKeeper 可以找到 -ROOT- 的 HRegion 托管的 HRegionServer。再通过 -ROOT- 表找到 .META. 表的 HRegion 位置。 .META. 表中存放着用户的表的 HRegion 切分信息。
整个流程为: Client → ZooKeeper → -ROOT- → .META. → 用户的表的 HRegion 。
HBase 0.96 以后
ROOT- 表被移除,直接将 .META. 表 HRegion 位置信息存放在 ZooKeeper 中,并将 .META. 表更名为 hbase:meta 。
此时整个流程为: Client → ZooKeeper → hbase:meta → 用户的表的 HRegion 。
读取数据流程
- Client 访问 ZooKeeper,获取 hbase:meta 所在 HRegionServer 的节点信息;
- Client 访问 hbase:meta 所在的 HRegionServer,获取 hbase:meta 记录的元数据后先加载到内存中,然后再从内存中查询出 RowKey 所在的 HRegion (HRegion 所在的 HRegionServer);
- Client 对 RowKey 所在的 HRegion 对应的 HRegionServer 发起读取数据请求;
- HRegionServer 构建 RegionScanner(需要查询的 RowKey 分布在多少个 HRegion 中就需要构建多少个 RegionScanner),用于对该HRegion 的数据检索;
- RegionScanner 构建 StoreScanner(HRegion 中有多少个 Store 就需要构建多少个 StoreScanner,Store 的数量取决于 Table 的ColumnFamily 的数量),用于对该列族的数据检索;
- 所有的 StoreScanner 合并构建最小堆(已排序的完全二叉树)StoreHeap:PriorityQueue;
- StoreScanner 构建一个 MemStoreScanner 和一个或多个 StoreFileScanner(数量取决于 StoreFile 数量);
- 过滤掉能够确定所要查询的 RowKey 一定不在的 StoreFileScanner 或 MemStoreScanner(布隆过滤器);
- 经过筛选后留下的 Scanner 开始做读取数据的准备,将对应的 StoreFile 定位到满足的 RowKey 的起始位置;
- 将所有的 StoreFileScanner 和 MemStoreScanner 合并构建最小堆 KeyValueHeap:PriorityQueue,排序的规则按照 KeyValue 从小到大排序;
- 从 KeyValueHeap:PriorityQueue 中经过一系列筛选后一行行的得到需要查询的 KeyValue。
写入数据流程
- Client 访问 ZooKeeper,获取 hbase:meta 所在 HRegionServer 的节点信息;
- Client 访问 hbase:meta 所在的 HRegionServer,获取 hbase:meta 记录的元数据后先加载到内存中,然后再从内存中查询出 RowKey 所在的 HRegion (HRegion 所在的 HRegionServer);
- Client 对 RowKey 所在的 HRegion 对应的 HRegionServer 发起写入数据请求;
- 建立连接后,首先将 DML 要做的操作写入到日志 HLog;
- 然后将数据的修改更新到 MemStore 中,本次操作结束。一个 HRegion 由多个 Store 组成,一个 Store 对应一个列族,Store 包括位于内存中的 Memstore 和位于磁盘的 StoreFile,写操作先写入 MemStore;
- 当 MemStore 数据达到阈值后(默认 128M),创建一个新的 MemStore;
- 旧的 MemStore 将刷写为一个独立的 StoreFile(HRegionServer 会启动 FlushCache 进程写入 StoreFile)并存放到 HDFS,最后删除 HLog 中的历史数据。