HBase 存储原理

2,601 阅读24分钟

存储模式

常见的关系型数据库大都使用行式存储,例如 mysqlOracle ,但 HBase 不一样,它使用的是列式存储

行式存储与列式存储

什么是行式存储,什么是列式存储?假如有这样一张表

那么行式存储是将一行数据存储在一起

列式存储是将一列数据存储在一起

底层存储结构如下,行式存储是存储玩一行,接着存储下一行,而列式存储是把一列数据存储在一起,由于列数据不存在一列存完了的概念,所以列数据与列数据之间不是紧挨着的,而是相互分离的。

优缺点

行式存储
优点

  • 随机读效率高
  • 优秀的事务支持 缺点
  • 需要维护大量索引,存储成本较高
  • 不能线性扩展

列式存储
优点

  • 当列中有数据重复时,会进行压缩,存储成本较低
  • 当查询多列数据时,由于不同的列是分开存储的,可以并行查询,提高查询效率 缺点
  • 事务支持的不好

应用场景

行式存储

  • 表与表之间有关联,需要连表查询
  • 需要联机事务处理
  • 查询需求大于存储需求

列式存储

  • 经常只访问某几列的数据时
  • 对数据压缩和线性扩展有要求的时
  • 存储需求大于读取需求时

总结,行式存储 适合OLTP(联机事务处理)场景,列式存储适合 OLAP(联机分析处理)场景

说明

上面对行式存储与列式存储的介绍是从比较宽泛的角度出发的,并不是说 HBase 是列式存储,它的内部设计就跟上面的完全一样。行式存储与列式存储更多的是两种存储思想。具体的实现,不同的产品有自己的实现方式。

HBase 的列族式存储

HBase 更像是列族式数据库而不是列式数据库

列族

列族HBase 官方术语中叫 Columns Family ,也有人它为列簇

什么是列族,其实在我们日常工作中经常使用过列族,比如我们有一张职工档案表,如图 其中 基本信息、工作信息就是列族,而姓名、性别、年龄则是归属在基本信息这个列族下的列。

注意,日常工作中我们会将一张表的列设计成多层,比如上面的职工档案表,基本信息可能还会分为个人基本信息和家庭基本信息,但是 HBase 只支持两层结构

HBase 的表由多个列族构造,每个列族又可以有若干个列,列族是 HBase 表的组成部分,而列不是,因此,创建表的时候,可以不声明列,但是不能不声明列族!

列族需要在创建表时就定义好,并且修改的不能太频繁,数量也不能太多,理论上是限制在几十个,但是实际中可能会更少。列族的名称必须是由可打印的字符组成,这个是与其他的值或名字的命名规范显著不同的地方。

Hbase 为什么要使用列族

一行数据有若干列组成,若干列又构成一个列族(column family),这不仅有助于构建数据的语义边界或者局部边界,还有助于给他们设置某些特性(如压缩),或者指示他们存贮在内存中,一个列族的所有列存贮在同一个底层的存储文件中。

列族设置

Hbase 的访问控制、磁盘和内存的使用统计都是在列族层面进行的,所以设计列族时一般是把相关的列或者是需要经常访问的列放在一起,组成列族,这样后期访问数据时会快速一些!

HBase 的列

HBase 的列属于某一个列族,列名使用列族名作为前缀,列可以再创建表时声明,也可以再表创建好后动态添加!

不同于列族需要先创建才能使用,列则灵活的多,可以在插入数据的时候指定

HBase 的表

Table = RowKey + Family + column + Timestamp + Value

HBase 的表由多个列族(Family) 构造,每个列族又可以有若干个列(column)。HBase 的表每条数据都有一个行键(RowKey),行键是该条数据在表中是唯一标示,类似于关系型数据库中的主键列中的每一个数据还有一个时间戳/版本号(Timestamp),HBase 支持多版本,当你开启多版本支持后,列中的数据发送变化时,旧数据会被保留,新旧数据通过一个时间戳作为区分,因此也可以理解为 Timestamp 是该数据的版本号!

除了存储历史数据,HBase 也是支持对列数据进行查询操作的

HBase 的存储结构

之前把 HBase 归类为列式存储,那是从外在功能表现层面分类的,如果从内部存储层面来分类的话, HBase 应该归类为 key value 型数据库

(Table,RowKey,Family,column,Timestamp) -> Value

在存储时,每个值的key由 表名、列族名、列名、行键和版本号组成

HBase 列的数据版本

HBase 列的数据支持多个版本,默认是3个

还是以职工档案表为例子

第一次插入插入了姓名、年龄、2个字段,在底层,他们是2个key value 键值对,并且每个键都有一个时间戳/版本号(Timestamp)

第二次补充了工资字段,并对应一个新的时间戳 t3

第三次更新了年龄字段,并对应新时间戳 t4

这条数据虽然有三个版本,但是它们都对应同一个 RowKey

现在再来看这条数据,姓名只有一个版本 t1 -> 张三,年龄有两个版本 t2 -> 22、t4 -> 23 ,工资只有一个版本 t3 -> 1000, 当查询数据时,默认展示列最新版本的数据,也就是说,我们查询 RowKey =1 时,结果为 张三 23 1000

数据存储原型

HBase 是通过如下的数据结构维护数据的

SortedMap<
    RowKey,List<
        SortedMap<
            Column,List<value,Timestamp>
        >
    >
>

第一个 SortedMap 是对 RowKey 进行排序,第二个 SortedMap 是对 Column 进行排序

ps:之前不是说 HBase 底层采用 key value 键值对的结构存储吗,现在怎么又变成来了这种数据结构?

其实这只是 HBase 的数据在不同抽象层次上的表现形式,就向任何数据在硬盘上最终都变现为连续或不连续的0、1一样,所以既可以说 HBase 是 key value 型的数据结构,也可以说他是这种带层级的数据结构!

HBase 表

HBase 建表语句

create '命名空间:表名',
{
NAME => '列族名1',
VERSIONS => '版本数量',
EVICT_BLOCKS_ON_CLOSE => 'false',
NEW_VERSION_BEHAVIOR => 'false',
KEEP_DELETED_CELLS => 'FALSE',
CACHE_DATA_ON_WRITE => 'false',
DATA_BLOCK_ENCODING => 'NONE',
TTL => '生存时间,单位秒,FOREVER 表示永不过期',
MIN_VERSIONS => '0',
REPLICATION_SCOPE => '复制范围',
BLOOMFILTER => 'ROW',
CACHE_INDEX_ON_WRITE => 'false',
IN_MEMORY => 'false',
CACHE_BLOOMS_ON_WRITE => 'false',
PREFETCH_BLOCKS_ON_OPEN => 'false',
COMPRESSION => '数据压缩算法,hbase 中常用的为 SNAPPY',
BLOCKCACHE => 'true',
BLOCKSIZE => '65536'
},
{
NAME => '列族名2',
VERSIONS => '1',
EVICT_BLOCKS_ON_CLOSE => 'false',
NEW_VERSION_BEHAVIOR => 'false',
KEEP_DELETED_CELLS => 'FALSE',
CACHE_DATA_ON_WRITE => 'false',
DATA_BLOCK_ENCODING => 'NONE',
TTL => 'FOREVER',
MIN_VERSIONS => '0',
REPLICATION_SCOPE => '0',
BLOOMFILTER => 'ROW',
CACHE_INDEX_ON_WRITE => 'false',
IN_MEMORY => 'false',
CACHE_BLOOMS_ON_WRITE => 'false',
PREFETCH_BLOCKS_ON_OPEN => 'false',
COMPRESSION => 'NONE',
BLOCKCACHE => 'true',
BLOCKSIZE => '65536'
}

也可以使用以下简洁的语句创建表

create '命名空间:表名','列簇1','列簇2'

这样创建出来的表,列簇各属性的默认值如下

{
NAME => '字段名(没有默认值)',
VERSIONS => '1',
EVICT_BLOCKS_ON_CLOSE => 'false',
NEW_VERSION_BEHAVIOR => 'false',
KEEP_DELETED_CELLS => 'FALSE',
CACHE_DATA_ON_WRITE => 'false',
DATA_BLOCK_ENCODING => 'NONE',
TTL => 'FOREVER',
MIN_VERSIONS => '0',
REPLICATION_SCOPE => '0',
BLOOMFILTER => 'ROW',
CACHE_INDEX_ON_WRITE => 'false',
IN_MEMORY => 'false',
CACHE_BLOOMS_ON_WRITE => 'false',
PREFETCH_BLOCKS_ON_OPEN => 'false',
COMPRESSION => 'NONE',
BLOCKCACHE => 'true',
BLOCKSIZE => '65536'
}

hbase 数据压缩

在建表时,可以给列族设置 COMPRESSION 属性以开启该列族的数据压缩(数据压缩是以列族为单位的)。这是一种用 CPU 时间换 IO速度和存储空间的方式。

COMPRESSION 属性有以下枚举值

  • 'NONE' : 不开启数据压缩
  • 'GZ' : 即 GZIP ,压缩率比 Snappy、LZO 高,对应的,压缩解压时占用的 CPU 资源也更多。推荐用于不经常访问的冷数据
  • 'LZO' : 与 GZIP 相比压缩率虽然不高,但使用的 CPU 资源更少,推荐用于经常访问的热数据,在2011 年 Google 推出 Snappy 之前,LZO 是默认推荐的设置
  • 'SNAPPY' :Snappy 具有与 LZO 相似的质量,但表现得更好,推荐用于经常访问的热数据。是目前默认推荐的设置
  • 'LZ4' : 与 LZO 相比,LZ4 的压缩率和 LZO 的压缩率相差不多,但是 LZ4 的解压 / 压缩速度更快

通常,您需要在较小的尺寸和更快的压缩/解压缩之间权衡选择,大多数情况下,默认情况下启用 Snappy 是一个不错的选择。

HBase 数据存储的目录结构

我们搭建 HBase 数据库的是时候需要指定数据存储的路径

hbase-site.xml

<property>
	<name>hbase.rootdir</name>
    <value>/hbase</value>
</property>

上面的 /hbase 就是数据存储的目录,hbase 会在该目录下创建一系列目录,它们主要有

  • /.tmp : 当对表做创建或删除操作的时候,会将表移动到 /.tmp 目录下做操作。它存储的是临时需要修改的数据结构。
  • /WALs : 预写日志目录,它里面是被 Hlog 实例管理的 WAL 文件
  • /archive : 存储表的归档与快照。hbase 在分割或合并完成后,会将 Hfile 文件 移到到该目录中,然后将原来的 Hfile 删除掉,这个操作是由 master 上的一个定时任务定期去处理
  • /corrupt : 存储的是损坏的日志文件,一般为空,如果不为空,说明 HBase 出了一些问题
  • /data : HBase 数据存储的核心目录,用于存储系统表和用户表的数据
  • /hbase.id : 存储着 hbase 启动之后,该实例在集群中的唯一标示
  • /hbase.version : 集群文件格式的版本信息,即 HFile 文件的版本信息
  • /oldWALs : 当 /WALs 目录中的 log 文件被持久化到存储文件中后,该日志文件会被移动到该目录中,并等待删除

说完了顶层目录,在来说一下 /data 目录,假如我们创建了一张名叫 test 表,存储在默认的 defaut 命名空间中,则 目录结构如下

  • data

    • ...

    • test

      • .tabledesc : 存储着表的元数据

        • .tableinfo.0000000001 : 表元数据的具体文件
      • .tmp : 临时数据目录,当表元数据发生变动的时候存储临时数据

        • ae7qd82jdaoeciq34fq6i3rc9jf38j3b
          • .regioninfo
          • cf
            • 0b3fie93834d93j1a93814892j7a23f

HBase 架构

img

如上图,HBase 主要由这几块组成

  • region server :负责 HBase 数据的存储和管理
  • zookeeper : 存储 meta 表
  • master : 管理 region server 和 zookeeper 中的 meta 表
  • client : 通过 zookeeper 中的 meta 表,找到需要访问的 region server 的地址和端口号

HBase 元信息表

HBase 元信息表被称为 Mete Table ,它本身也是一张 HBase 表,所以它也有 Row Key 、列族等。Mete Table 中存储着系统中所有的 region 信息。region 是 HBase 中一个很重要的概念,它是存储用户数据的最基本单元,在之后的章节中会详细介绍。

Mete Table 存储在 ZooKeeper 上,客户端需要先访问 ZooKeeperMete Table 找到数据所在的 region 地址后,在访问 region

meta 表结构如下

meta 表只有一个列簇 — info,它有4个列

  • regionInfo :当前 region 的 startKey 与 endKey,name 等信息
  • server:region 所在服务器及端口
  • serverStartCode:region server 的开始时间
  • seqnumDuringOpen:

值得说明的是 Row key ,它的格式为 TableName,StartKey,Timestamp.EncodedName

  • TableName:表名称
  • StartKey:表示当前 表 的 region 中存储的第一个 rowkey。如果这个地方为空的话,表明这是 table 的第一个 region。并且如果一个 region 中 StartKey 和 EndKey 都为空的话,表明这个 table 只有一个 region;
  • Timestamp:Region 创建的时间戳;
  • EncodedName:TableName,StartKey,Timestamp 字符串的 MD5 Hex 值。

当 region 发送变化时(region挂掉、region 分隔、region 合并等)HBase 的 master 服务会更新 Mete Table

Mete Table 相当于 hbase 表的顶级索引。

HBase 中的 LSM 存储思想

LSM

日志结构合并树 (Log-Structured Merge-Tree , LSM),并不属于某一个具体的数据结构,一般是由两个以上的数据结构组成的,并且每一个数据结构对应不同的存储介质

举例说明,假设有这样一种 LSM ,它有两种数据结构 c0 、c1,其中 c0 存储在内存介质中,c1 存储在磁盘介质中。当有数据插入时,先暂存在 c0 中以获得更好的存储性能,当 c0 中的数据达到预设阈值时,再将数据全部持久化到c1。

这种两层的 LSM 一般都是一层数据结构存储在内存中,另一层数据结构存储在硬盘中。当然 LSM 不一定非要设计成两层,也可以设计成三层,比如 c0、c1、c2 分别存储在内存、硬盘、硬盘中,其中 c2 是对 c1 的聚合归档。

LSM 更多是一种数据结构的设计思想, 可以根据具体情况自由调整。

HBase 中的 LSM 存储实现

HBase 采用三层 LSM 存储结构

第一层 : 内存和日志

为了提高随机写性能,当用户数据到达 region server 时,不会直接写入磁盘中,而是先写入日志和内存中,写入日志中是防止内存断电等情况造成数据丢失。

第二层 : 硬盘

当内存中的数据量达到阈值,异步线程将数据刷写到硬盘上,形成 storeFile 文件

第三层 : 硬盘

随着不断的刷新,硬盘中的小文件越来越多,这不利于管理和随机查询,在合适的时机时,启动线程对小文件执行合并操作形成一个大文件。

HBase 存储模块

RegionServer 包含多个 Region 和 一个 HLog

  • Region : 也称为 HRegin ,它是存储用户数据的最小单元,它对应一张表的部分行数据一张HBase 表可能会根据行键分成多段存储在不同的 RegionServer 中的不同 Region 上,以实现负载均衡。
  • HLog : HLog 文件存储在硬盘上,用于实现 WAL(预写日志)。当用户数据进入到 RegionServer 后最先会被保存在 HLog 中,以实现高可用。当服务当机等情况发生后。可用通过回放日志恢复到之前的状态。

一个 RegionServer 可以包含多个 Region ,一个 Region 又包含多个 storestore 对应表中的列族 ,表中有多少个列族, 该表的 Region 中就有多少个 store

store 由一个 MemStore 和多个 storeFile组成

  • MemStore : 它是存储在内存中的数据结构,当用户数据已经保存到 HLog 后,数据才会进入到 region ,然后先会刷新到对应 storeMemStore 中,MemStore 达到阈值之后,才会刷新到 storeFile
  • storeFile/HFile : storeFileMemStore 中数据达到阈值之后刷写出来的文件。存储表数据的文件。storeFile 文件最终会被转换成 HFile 格式存储在 HDFS

HLogMemStore 共同构成了 HBase 中的 LSM(日志结构合并树) 的第一层,它保证了数据可以快速、可靠的写入HBase

storeFile/HFile 共同构成了 HBase 中的 LSM(日志结构合并树) 的第二层,它保证了不可靠数据的持久化存储

HBase region

RegionHBase 分布式存储和负载均衡的最小单元,一个 Regionserver 服务中有多个 Region,每一个 Region 对应一张表的部分行数据(一张表的数据可能会分成多段存储在不同的 RegionServer 中的不同 Region 上)。

当数据不断插入到达 Region 阈值时,Region 会被水平拆分成俩个 Region 。当某一个 Region Server 挂掉,或 master 触发负载均衡策略时, Region 可能会从一个 Region Server 移动到另一个 Region Server

Region Server 拆分 Region 的过程如下

  • 先将 Region 下线,然后拆分
  • 然后将拆分出来的 Region 的信息加入到元信息表 Mete Table
  • 最后上线拆分出来的 Region

每一个 Region 会有三个信息标示自己

  • tableName : 表名称
  • startRowKey : 从哪个 RowKey 开始
  • createTime : Region 的创建时间,也是最早的一条数据插入进来的时间。也就是说 Region 是按需创建的

Region 特点

RegionHBase 分布式存储和负载均衡的最小单元,但不是存储的最小单元。

Region 数据过多会导致数量下降,Region 数量太少会影响并行查找速度以及压力不够分散,Region 最好不要低于集群中节点的数量

Region 的拆分过程是不可见的,因为 master 不会参与

HBase HFile

HBase 将数据以 HFile 文件格式保存咋 HDFS

HFile 与其他组件

store、memStore、storeFile 、HFile 之间的关系如下

一个 Region 中可能有多个 store ,每一个 store 代表一个列族,一个 store 有一个 memStore ,它是一个内存型的数据结构,保存用户修改的数据。 store 还可能有多个 storeFile ,它是文件系统级别的数据结构。

storeRegion 管理,用于维护列族数据,Region 中存储的表有几个列族,该 Region 就有几个 storeHBase 会根据 store 大小去判断是否拆分 region

memStore 默认大小为 64 MB ,当数据满后,HBase 会将 memStore 中的数据刷写到 storeFile 中,而 storeFile 底层采用 HFile 文件格式存储。

HFile(StoreFile) 文件

StoreFile 以 HFile 格式保存在 HDFS 上。HFile 是 Hadoop 的二进制格式文件。实际上 StoreFile 就是对 HFile 做了轻量级包装,即 StoreFile 底层就是 HFile

HFile 文件是 HBase 最基础的存储单元,它是用户数据的实际载体,它底层是 HDFS 的二进制格式文件,存储着 key - value 格式的数据

HFile 文件结构如上,一共包含4个部分,分别用不同颜色表示

  • Scanned block section : 在顺序扫描 HFile 的时候,这个部分的数据块会被读取,用户数据存储在其中
  • Non - scanned block section : 在顺序扫描 HFile 的时候,这个部分的数据块不会被读取,主要包含一些元数据
  • Load - on - open section : HFile 文件的元数据信息,在 Region Server 启动的时候,会加载到内存中
  • Trailer : 记录了 HFile 寻址信息、各个部分的偏移量、版本等,目前 HBase 使用的 HFile 是 2.0 版本的

Data Block

用户数据存储在 HFile 文件的 Scanned block section 区域中,具体则是存储在 Scanned block sectionData Block 中。

Data Block 存储着 key - value 格式的数据

如上图,Data Block 中包含多个 key - value ,每个 key - value 又由4个部分组成

  • Key Length : key 的长度
  • Value Length : value 的长度
  • key : key 由多个信息组成,之后会单独细讲
  • value : 某张表的某一行的某一个列族中的某个列的数据

Data Blockkey - valuekey 由多个部分组成

  • Row Length : 行键的长度
  • Row : 行键,即 HBase 表的每行数据中的 RowKey
  • ColumnFamilyLength : 列族名长度
  • ColumnFamily : 列族名
  • ColumnQualifierLength : 列标示符长度
  • ColumnQualifier : 列标示符,即列名称
  • Timestamp : 这个值的时间戳,也可以理解为这个值的版本号
  • KeyType : key 的类型,用于给数据做标记,当扫描时,遇到 Delete 标记跳过这条 key - value
    • Put : 添加数据
    • Delete : 删除整行
    • DeleteColumn : 删除整个列族
    • DeleteFamily : 删除整列

HBase Compaction

在上面的章节中讲到 HBase 采用了 3 层 LSM ,第一次是将数据存入 memStore (内存)中,第二层是将 memStore 中的数据刷写到 StoreFile 文件中,而 HBase Compaction 则承担了 LSM 的第三层,将多个小的 StoreFile 文件合并成一个大的文件。

合并的区域
HBase Compaction 会在特定的时期,合并某个 RegionServer 的某个 Region 的某个 Store 中部分的 storeFile文件。也就是说 HBase Compaction 的合并操作不会垮两个 Store

合并的过程
HBase Compaction 先从这些待合并的文件中读出 key - value ,然后将这些数据重新排序后写入到新的 HFile 中

为什么要设计HBase Compaction

随着系统的运行,文件会越来越多,文件太多,会导致维护困难,而且不利于数据查询,会影响数据查询的效率(因为可能需要打开多个文件才能找到数据,而打开、关闭文件是要消耗系统资源的),因此需要合并

HBase Compaction 在合并过程中会清理三类数据

  • KeyType 类型为 delete 的数据
  • ttl 过期的数据
  • 版本过期的数据,比如某张表的最大版本设置为3,当某条数据的第4个版本插入时,最老的第一个版本不会立即删除,而是在合并时才被删除

两种 HBase Compaction

HBase Compaction 分为两种 Minor Compaction (小合并) 和 **major Compaction **(大合并)

Minor Compaction 是选取相邻的 storeFile 合并成一个大的 storeFile

major Compaction 是将 store 中所有的 storeFile 合并成一个大的 storeFile,由于 major Compaction 合并的文件较多,持续的时间较长,整个过程会消耗大量的系统资源,对上层业务会有比较大的影响,因此在生产环境中,一般都会将自动触发的 major Compaction 关掉,改为手动在业务低锋期触发。

Compaction 触发时机

HBase Compaction 触发因素有很多,最常见的因素有三种

  • MemStoreFlash : 每次 MemStore 数据满了刷写到 HFile 之后,都会对当前 Store 中的文件进行判断,如果文件的数量到达阈值,就会触发 Compaction ,这也是 HBase Compaction 触发的源头

  • Compaction CheckCompaction Check 线程会在后台周期性的检查,它首先会检查文件数量,当文件的数量到达阈值,就会触发 Compaction ,其次还会检查当前 StoreHFile 的最早更新时间早于某个值(这个值我们叫做 MCTime),就会触发 major Compaction

MCTime 是一个浮动值,默认浮动区间为 [7 - 7 * 0.2 , 7 + 7 * 0.2]。

其中 7 是配置项 hbase.hregion.majorcompaction 的值

0.2 是配置项 hbase.hregion.majorcompaction.jitter 的值

默认在 7天左右就会进行一次 major Compaction ,如果想要禁用自动 major Compaction ,只需要将 hbase.hregion.majorcompaction 的值设为 0 即可

  • 手动触发 : 一般手动触发都是执行 major Compaction

HBase WAL(预写日志)

HBase WAL 类似于 mysqlbinlog,它记录了所有的数据改动,一旦服务崩溃,可以通过重放 WAL 恢复到崩溃之前的状态。

当对 Regin Server 写入数据的时候,如果写入 WAL 失败,整个操作都会被认为失败的。

如上图,HBase Client 在执行 putdelete 等命令时,首先都要记录 log ,然后再到 Region

HBase WALHbase 数据提供灾难恢复,提高了数据库的可靠性,同时通过同步日志文件,可以实现远程备份功能

HLog

HBase WAL 机制是通过 Hlog 来实现的,HLogHBase WAL 的具体实现类,并且一个 Regin Server 服务只有一个 HLog 实例。当 Regin 初始化的时候,HLog 实例会当做一个参数传递给 Regin 的构造函数,这样一来 Regin 就得到了 HLog 的应用,可以实现打日志

出于对性能的考虑,put()、delete()、incr() 有一个开关函数 setReadToWAL(boolean b) ,参数是一个布尔类型,可以设置为 false 来表示是禁用 wal

WAL 使用 Hadoop 的序列化文件,将日志存储为 key - value 格式的数据集 , 其中 value 部分为用户的 put、delete 等操作的数据,keyHLogKey,它由以下内容组成

  • Region : 数据归属的 Region
  • TableName : 数据归属的表
  • SequenceNumber : 序列号
  • WriteTime : 日志写入时间

HLogSyncer

HLogSyncer 是日志同步刷写类,HBase WAL 默认是将日志直接写入文件系统,但有时候为了提高性能,会暂时将日志写入到内存中,这时候就需要用一个异步的线程去实现将内存中的日志数据刷写到文件系统中,HLogSyncer 就是负责这件事。

HLogSyncer 有两种策略

  • 定时刷写 : 根据设定的时间间隔去刷写
  • 内存阈值 : 当内存达到阈值时,刷写到文件系统

HLogRoller

HBase Log 文件大小可以通过配置文件设置,默认为 1个小时,也就是说每个小时产生一个新的 log 文件

HLogRoller 后台线程负责整理这些日志文件,它主要有以下功能

  • 在特定的时间滚动日志,形成新的文件,防止单个文件过大
  • 根据 HLogSequenceNumber 对比已经持久化的序列号,删除旧的、不需要的日志

HBase 数据存取流程

存储

客户端

存储数据(PutDelete)时, HBase Client 的主要流程如下

  1. HBase Client 请求 Zookeeper 获取 HBase Mete Table(元信息表),从 Mete Table 中获取 Region Server 的地址
  2. 根据 Region Server 的地址找到 Region Server 服务
  3. 获取 Region Server 服务中各 Region 的元信息(Table Name、startRowKey等)
  4. 用户提交 PutDelete 请求时,HBase Client 先将请求放入本地 buffer 中(autoFlash 默认为 true,也可以配置成关闭,关闭后,一次一提交),直到满足阈值条件(默认2M)后,会通过异步批量提交到 HBase 服务端

服务端

存储数据(PutDelete)时, HBase Server 的主要流程如下

  1. 当数据到达 Region Server 的某一个 Region 后,首先会获取 RowLock(行锁),并创建共享锁。Hbase 通过 RowLock 来保证对一行数据的操作的原子性
  2. 将数据写 HLog,注意此时**(持有 RowLock 时),并不会将 HLog 刷写到硬盘上**。HBase 通过 HLog 实现 WAL 机制
  3. 完成写日志后,在按照列族将数据写入到列族对应 Stroe 中的 MenStore 里。先写日志再写缓存,这样即使服务宕机,也可以通过日志恢复
  4. 释放 RowLock 及共享锁,将 HLog 刷写到硬盘。先释放 RowLock 再刷写 HLog 到硬盘是为了减小持有行锁的时间,提高写性能
  5. 如果 HLog 刷写到硬盘失败,执行回滚,将各 MenStore 中的数据删除
  6. MenStore 中的数据超过阈值(默认64M),启动异步线程,刷写到硬盘上,形成多个 storeFile
  7. storeFile 数量增长到阈值时,触发 HBase Compaction 将多个 storeFile 合并成一个 storeFile ,同时删除时间过期、版本过期、Delete 标记的数据
  8. 经过多轮 HBase Compaction 之后,当单个 storeFile 大小超过阈值时,触发 HBase Split ,将当前 **Region ** 分裂成两个 **Region ** ,并通知 Master 服务。
  9. Master 会下线原来的 **Region ** ,并将新分裂出来的两个 **Region ** 根据负载均衡分配到 Region Server 上(可能会分配到不同的 Region Server 上)

读取

客户端

HBase Client 读取数据(Scan)时, 除了不会将请求放入本地 buffer 中,其他地方跟存储时,基本一致,不做过多介绍

服务端

读取数据(Scan)时, HBase Client 的主要流程如下

  1. 当数据到达 Region Server 后,根据 Scan 的内容,HBase 会在 Region ServerRegion 上构建 Region Scanner
  2. Region Scanner 会根据列族,构建 Store Scanner,负责对 列族的数据检索 (有多少个列族,就会构建多少个 Store Scanner)
  3. 每个 Store Scanner 会为当前 Store 中的每个 HFIle 构造一个 Store File Scanner 用于执行实际的文件检索,此外,还会对 Store 中的 Mem Store 构建 Mem Store Scanner 用于检索 Mem Store 中的数据
  4. Mem Store ScannerStore File Scanner 扫描到的 Key - value 结构的数据封装之后返回给客户端

**HBase ** 读取数据的核心是 3层 Scanner

  • 第一层 : Region Scanner
  • 第二层 : Store Scanner
  • 第三层 : Mem Store ScannerStore File Scanner

一个 Region Scanner 由多个(列族个) Store Scanner 组成,一个 Store Scanner,由多个 Store File Scanner 以及一个 Mem Store Scanner 组成