Apache-Hbase基本架构及工作流程

1.基本概念
简介
Apache HBase(Hadoop DataBase)是一个开源的、高可靠性、高性能、面向列(这里指列族,非列式存储)、可伸
缩、实时读写的分布式数据库,其设计思想来源于 Google 的 BigTable 论文。利用 Hadoop HDFS 作为其文件存储系统,利
用 ZooKeeper 作为其分布式协同服务。主要用来存储非结构化和半结构化的松散数据(列式存储 NoSQL 数据库)。
HBase 良好的分布式架构设计为海量数据的快速存储、随机访问提供了可能,基于数据副本机制和分区机制可以轻松
实现在线扩容、缩容和数据容灾,是大数据领域中 Key-Value 数据结构存储最常用的数据库方案。
注意 :Hbase是列族数据库,不是列式数据库。
总结: Hbase是运行在HDFS上的面向列(列族)的数据库管理系统。
应用
适合Hbase的应用:
- 存储业务数据:车辆 GPS 信息,司机点位信息,用户操作信息,设备访问信息。
- 存储日志数据:架构监控数据(登录日志,中间件访问日志,推送日志,短信邮件发送记录),业务操作日志信息。
- 存储业务附件:UDFS 系统(去中心化文件系统)存储图像,视频,文档等附件信息。
Hbase和RDBMS的区别:
| 属性 | HBase | RDBMS |
|---|---|---|
| 数据类型 | 只有字符串 | 丰富的数据类型 |
| 数据操作 | 增删改查,不支持 JOIN | 各种各样的函数与表连接 |
| 存储模式 | 按列族存储 | 表结构和行式存储 |
| 数据保护 | 更新后仍然保留旧版本 | 替换 |
| 可伸缩性 | 容易增加节点 | 需要中间层,硬性性能 |
2.数据模型(Importance)
概念
在 HBase 表中,一条数据拥有一个**全局唯一的主键(RowKey)**和任意数量的列(Column Qualifier),每个列的数据
存储支持多个版本(Version),一列或多列组成一个列族(Column Family),同一个列族中列的数据在物理上都存储在
同一个 HFile 中。这样基于列存储的数据结构有利于数据缓存和查询。

以,在 HBase 中定位一条数据需要通过:RowKey → Column Family → Column Qualifier → Version。
NameSpace(安装后默认带的库)
- default:没有明确指定命名空间的表将自动落入此命名空间。
- hbase:系统命名空间,用于包含Hbase的内部表和元数据表。
Table
- Table和关系型数据库中的表一个意思由行列组成。
RowKey
- 存储数据时,数据会按照 RowKey 的字典序排序
存储,所以设计 RowKey 时,要充分利用排序存储这个特性,将经常一起读取的行存放到一起。 - 问 HBase 数据的方式有三种:
- 基于 RowKey 的单行查询;
- 基于 RowKey 的范围查询;
- 全表扫描查询。
Column Family
Column Family 即列族,HBase 基于列划分数据的物理存储,同一个列族中列的数据在物理上都存储在同一个 HFile
(理解为一个文件)中。一个列族可以包含任意多列,一般同一类的列会放在一个列族中,每个列族都有一组存储属性:
- 是否应该缓存在内存中;
- 数据如何被压缩或行键如何编码等。
HBase 在创建表的时候就必须指定列族。HBase 的列族不是越多越好,官方推荐一个表的列族数量最好小于或者等于
三,过多的列族不利于 HBase 数据的管理和索引。
Column Qualifier
族的限定词,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识。使用的时候必
须 列族:列 ,列可以根据需求动态添加或者删除,同一个表中不同行的数据列都可以不同。
Timestamp
Timestamp是用于实现多版本的,使用不同Timestamp来标识相同RowKey但版本不同的数据,相同RowKey的数据根据Timestamp倒序排列,默认查询最新版本,用户也可以指定Timestamp查询对应的版本。
HBase 通过 RowKey 和 Column Family,Column Qualifier 来确定一个存贮单元,然后再通过时间戳来进行索引。时间
戳的类型是 64 位整型,时间戳默认是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值,如果应用程序要避免
数据版本冲突,就必须自己生成具有唯一性的时间戳。
了避免数据存在过多版本而造成管理(包括存贮和索引)负担,HBase 提供了两种数据版本回收方案:
- 一是保存数据的最后 n 个版本。
- 二是保存最近一段时间内的版本(比如最近七天)。
Cell
Cell 由 Row,Column Family,Column Qualifier,Version 组成。Cell 中的数据是没有类型的,全部使用字节码形式存
贮,因为 HDFS 上的数据都是字节数组

3.架构模型(Importance)(CP架构)
HBase 可以将数据存储在本地文件系统,也可以存储在 HDFS 文件系统。在生产环境中,HBase 一般运行在 HDFS
上,以 HDFS 作为基础的存储设施。用户通过 HBase Client 提供的 Shell 或 Java API 来访问 HBase 数据库,以完成数据的
写入和读取。HBase 集群主要由 HMaster、HRegionServer 和 ZooKeeper 组成。

zookeeper关系
- Hbase通过Zookeeper来选举HMaster,监控ReginonServer(心跳)
- 一般在HMaster会在搭建时就设置好主服务器
- 实现主从节点的故障迁移(Fallover)
- 维护元数据和集群配置:
- 存放整个 HBase 集群的元数据以及集群的状态信息;
- 存储所有 HRegion 的寻址入口(hbase:meta 元数据表),存储所有的的元数据信息;
- 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family;

Hbase客户端
HBase Client 为用户提供了访问 HBase 的接口,可以通过元数据表(客户端负责发送请求到数据库)来定位到目标数
据的 HRegionServer。客户端连接的方式有很多种
- HBase Shell
- Java API(可以直接发送请求):
- DDL:数据库定义语言(表的建立,删除,添加删除列族,控制版本)
- DML:数据库操作语言(增删改)
- DQL:数据库查询语言(查询,全表扫描,基于主键,基于过滤器)
- Client 维护着一些 Cache 来加快对 HBase 的访问,比如 HRegione 的位置信息。

HMaster(负责Hbase集群管理工作)
- 管理分配:
- 管理和分配 HRegion,负责启动的时候分配 HRegion 到具体的 HRegionServer,又或者在分割 HRegion 时
关于新 HRegion 的分配。管理用户对 Table 结构的 DDL(创建,删除,修改)操作。 - 表的元数据信息存储在 ZooKeeper
- 表的数据存储在 HRegionServer 上(实际存储在 HDFS 上)
- 管理和分配 HRegion,负责启动的时候分配 HRegion 到具体的 HRegionServer,又或者在分割 HRegion 时
- 负载均衡:一方面负责将用户的数据均衡地分布在各个 HRegionServer 上,防止 HRegionServer 数据倾斜过载。另一方面负责将用户的请求均衡地分布在各个 HRegionServer 上,防止 HRegionServer 请求过热;
- 维护数据: 发现失效的 HRegion,并将失效的 HRegion 分配到正常的 HRegionServer 上。当某个 HRegionServer 下线时迁移其内部的 HRegion 到其他 HRegionServer 上。
- 权限控制:

HRegionServer
- HRegionServer 直接对接用户的读写请求,是真正干活的节点,属于 HBase 具体数据的管理者。
- 实时和 HMaster 保持心跳,汇报当前节点的信息,包括给Zookeeper汇报心跳
- 当接收到 HMaster 的命令创建表时,会分配一个 HRegion 对应一张表;
- 负责切分在运行过程中变得过大的 HRegion;
- 当 HRegionServer 意外关闭的时候,当前节点的 HRegion 会被其他 HRegionServer 管理
- 维护HMaster分配的Region,并处理这些请求
- 当客户端操作时,负责与客户端建立连接。
- WAL:记录了数据写入,更新日志,被用于数据恢复
- MemStore:写缓存,数据首先会被写入到 MemStore 中。每个 HRegion 的每个 Column Family 都会有一个
MemStore。 - 负责与底层的 HDFS 交互,存储数据(HLog、HFile)到 HDFS。
- BlockCache:读缓存,在内存中存储了最常访问的数据,采用 LRU 机制进行淘汰。

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

HRegion
- 一个region里有若干列组
- 存储列簇数据
负载均衡

Split
-
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都对应一个列族,Store包含一个MemStore和多个Store组成
- MemStore:数据会先写入此,当超过阈值(128M),开始刷写到StoreFile内持久化存储,客户端会先在MemStore中寻找数据,没有则会在StoreFile中寻找数据(HDFS存储)
- MemStore 中的数据写到文件后就是 StoreFile,StoreFile 底层是以 HFile 格式保存的。HBase 以 StoreFile 的
大小来判断是否需要切分 HRegion。当一个 HRegion 中所有 StoreFile 的大小和数量都增长到超过指定阈值时,HMaster
会把当前 HRegion 分割为两个,切分后其中一个 HRegion 会被转移到其他的 HRegionServer 上,实现负载均衡。 - HFile 和 StoreFile 是同一个文件,只不过站在 HDFS 的角度称这个文件为 HFile,站在 HBase 的角度就称这个文
件为 StoreFile


Hfile(最终存储的文件HDFS存储)
-
每个Hfile由多个Block组成。
-
每个KeyValue又由多个KeyValue数据组成,KeyValue对象是数据存储的核心。
注意:KeyValue 对象不跨 Block 存储,假如这里有一个 KeyValue 的大小为 8M,即使 Block-Size=64KB,当读取该
KeyValue 的时候也是以一个连贯的 Block 进行读取。 -
Key Length:存储 Key 的长度,4 个字节。
-
Value Length:存储 Value 的长度,4 个字节。
-
Key(还可以被拆解):存储数据的 Key,由 Row Key Length,Row Key,Column Family Length,Column Family,
Column Qualifier,TimeStamp,Key Type 组成。 -
Value:存储 Column Qualifier 的值(用户写入的实际数据)。


- 在Hfie中寻找数据时,主要先遍历key,然后匹配RowKey与CQ,并返回Value(cqv)。
4.读写流程
三层索引
- Hbase0.96以前。
- 个流程为: Client → ZooKeeper → -ROOT- → .META. → 用户的表的 HRegion。

- Hbase0.96以后,Root表被移除,此时时整个流程为: Client → ZooKeeper → hbase:meta → 用户的表的 HRegion 。

读数据流程

- Client 访问 ZooKeeper,获取 hbase:meta 所在 HRegionServer 的节点信息;
- Client 访问 hbase:meta 所在的 HRegionServer,获取 hbase:meta 记录的元数据后先加载到内存中,然后再从内存中查
询出 RowKey 所在的 HRegion (HRegion 所在的 HRegionServer); - 再根据元数据匹配数据的位置,并返回数据,缓存到64k的cacheblock中。
- 方便查询
5.写入数据流程
- Client 访问 ZooKeeper,获取 hbase:meta 所在 HRegionServer 的节点信息;
- Client 访问 hbase:meta 所在的 HRegionServer,获取 hbase:meta 记录的元数据后先加载到内存中,然后再从内存中查
询出 RowKey 所在的 HRegion (HRegion 所在的 HRegionServer); - Client 对 RowKey 所在的 HRegion 对应的 HRegionServer 发起写入数据请求;
- 把要写的数据先写入日志。
- 在更新数据到MemStore中,操作结束。
- 当MemStore数据达到阈值(128M),创建一个新的MemStore。
- 旧的 MemStore 将刷写为一个独立的 StoreFile(HRegionServer 会启动 FlushCache 进程写入 StoreFile)并存放到
HDFS,最后删除 HLog 中的历史数据。 - 当一个 HRegion 所有 StoreFile 的大小和数量超过一定阈值后,会把当前的 HRegion 分割为两个,并由 HMaster 分配到
相应的 HRegionServer 服务器,实现负载均衡。
6.刷写流程
触发机制
内存阈值
- 内存阈值触发刷新(默认128M)
- 为了防止内存刷写时还有数据在写入导致错误,MemStore会先拍摄快照并上锁。
- 刷写完成后没有问题再释放锁。
- 果我们的数据增加得很快,达到了 hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier(默
认为 4) 的大小,也就是 128 * 4 = 512MB 的时候,除了触发 MemStore 刷写之外,HBase 还会在刷写的时候阻塞所有写入该
Store 的请求。 - 整个RegionServer内的MemStore占用内存总和大于阈值时也会触发刷写。
- 触发RegionServer级别的刷写会导致整个RegionServer阻塞。
日志阈值
- Hbase使用了WAL(日志先行机制),数据到达RegionServer时会先写入日志再写入MemStore,日志也需要持久化存储,所以日志到达一定的量时也会触发刷写。
- 相关公式为:Math.max(32, hbase_heapsize * hbase.regionserver.global.memstore.size * 2 / logRollSize)。
定期刷写
- Hbase支持定时刷写,例如一个小时刷写一次,一般会调大参数。
- 参数过小会导致刷写频繁和小文件过多(影响随机读写性能)。
更新频率
- 如果某个RegionServer一直没有触发任何阈值,但是又一直更新就会触发刷写。
- 默认为3000000次。
手动刷写
-
可以在Shell中使用
flush命令刷写。hbase> flush 'TABLENAME' hbase> flush 'REGIONNAME' hbase> flush 'ENCODED_REGIONNAME' hbase> flush 'REGION_SERVER_NAME'以上所有条件触发的刷写操作最后都会检查对应的 Store 包含的 StoreFiles 文件数是否超过
hbase.hstore.blockingStoreFiles 参数配置的个数,默认为 16。如果满足这个条件,那么当前刷写会被推迟到
hbase.hstore.blockingWaitTime 参数设置的时间后再刷写。
如果是阻塞刷写,HBase 还会请求 Compaction 压实处理或者 Split 分割操作
刷写策略
- FlushAllStoresPolicy策略:Region级别的刷写,当一个MemStore开始刷写,当前Region下的其他MemStore也会刷写。
- FlushAllLargeStoresPolicy策略:判断Region下的所有MemStore是否超过内存阈值,超过的一起刷写,阈值计算公式: flushSizeLowerBound = max((long)128 / 3, 16) = 42 。
- FlushNonSloppyStoresFirstPolicy策略:
- 将Region中的MemStore划分到了两个HashSet中,如果内存大于上述阈值计算公式即刷写,小于不做任何处理。
- 如果并不满足这两种情况,会退化到第一种策略。
刷写流程
- prepareFlush 阶段:第一步先拍摄快照,并上锁UpdateLock,阻止客户端继续写。
- flushCache 阶段:
- flushCache 阶段:将 prepareFlush 阶段创建好的快照写到临时文件里面,临时文件是存放在对应 HRegion 文件夹下面
的 .tmp 目录里面。 - commit 阶段:将 flushCache 阶段生产的临时文件移到(rename)对应的列族目录下面,并做一些清理工作,比如删
除第一步生成的 Snapshot。
- flushCache 阶段:将 prepareFlush 阶段创建好的快照写到临时文件里面,临时文件是存放在对应 HRegion 文件夹下面
7.数据合并

- 因为刷写不可避免会有小文件被存储所以需要合并数据。
合并分类
HBase 根据合并规模将压实 Compaction 分为了两类:Minor Compaction 和 Major Compaction。
- Minor Compaction(小压实)
- 选取相邻的一些文件合并为更大的。
- 过程中不删除任何数据。
- 会对minVersion=0 并且设置 TTL 的过期版本数据进行清理。
- Major Compaction。(大压实)
- 清除三类数据:被删除的数据、TTL 过期数据、版本号超过设定版本号的数据。
- 过程会消耗大量资源简易在低峰期触发。
Minor Compaction:快速让小文件合并成大文件
Major Compaction:清理大文件不必要的数据,释放空间
合并时机
- 发 Compaction 的方式有三种:MemStore 刷盘、后台线程周期性检查、手动触发
MemStore刷盘时
- 每次执行完刷盘,会对当前Store中的文件进行判断,大于配置就会触发压实
周期检查
- 周期性检查可以配置,当文件数大于配置时就会触发 Compaction,如果不满足会继续检查是否满足上次刷写到现在是否
默认:7天满足也会触发Compaction。 - 周期性检查线程默认2h46m40s触发一次
手动执行
- 因为很多业务担心自动 Major Compaction 影响读写性能(可以选择直接关闭),因此会选择低峰期手动触发;
- 用户在执行完 alter 操作之后希望立刻生效,手动执行触发 Major Compaction;
- HBase 管理员发现硬盘容量不够的情况下手动触发 Major Compaction 删除大量过期数据。
合并策略
HBase 主要有两种 Minor Compaction 策略:RatioBasedCompactionPolicy(0.96.x 之前) 和 ExploringCompactionPolicy(当
前默认)。
- RatioBasedCompactionPolicy(基于比列的合并策略):从老到新逐一扫描 HFile 文件,满足以下条件之一停止扫描:当前文件大小 < 比当前文件新的所有文件大小总和 * Ratio(高峰期1.2,非高峰期5),当前所剩候选文件数 <= 阈值(默认为3)。例如:当前文件 2G < 所有文件大小总和 1G * 1.2,高峰期不合并,非高峰期合并;
- ExploringCompactionPolicy 策略:寻找最优解,在扫描时找到合适的文件集合就停止扫描,再等待文件合并数最多或者合并文件数相同的情况下文件较小的进行合并。

- FIFO Compaction 策略:收集过期文件并删除,对应业务的列族必须设置有 TTL;
- Tier-Based Compaction 策略(分层策略):根据文件的新老程度划分等级,每个等级有自己的策略。
- Stripe Compation 策略(条纹策略):在Store内根据RowKey划分并合并,划分出来的一个Range就相当于一个小Region。
8.数据切分
切分原因
- 数据分布不均匀所以需要使用切分,对数据进行负载均衡。
- 数据需要压实,如果不均匀会导致压实的性能损耗。
- HBase 的数据写入量也是很惊人的,每天都可能有上亿条的数据写入不做切分的话一个热点 HRegion 的新增数据量就有几十G,用不了多长时间大量读请求就会把单台 HRegionServer 的资源耗光。
切分流程
寻找切分点
- 遍历Store找到最大的,再在这个Store中找到最大的Hfile,定位这个文件的中心位置的RowKey,作为Hregion的切分点。
开启切分事物
- prepare 阶段
- execute 阶段
- rollback 阶段
切分优化
- 根据RowKey执行预分区。
- 在建表之前创建切分点。
9.优化
表优化
预分区
- Pre-Creating Regions。默认情况下,在创建 HBase 表的时候会自动创建一个 HRegion 分区,当导入数据的时候,所有
的 HBase 客户端都会向这一个 HRegion 写数据,直到这个 HRegion 足够大了才进行切分,所以建表时一般会提前预分区,
这样当数据写入 HBase 时,会按照 HRegion 分区的情况,在集群内做数据的负载均衡
RowKey
- 避免热点:采用散列策略(如 MD5 或时间戳反转)分散写入压力。
- 保持有序性:对查询场景需要的字段(如时间)设计 RowKey 的前缀。

