《HBase 不睡觉》第五章 - HBase 内部探险

《HBase 不睡觉书》是一本让人看了不会睡着的 HBase 技术书籍,写的很是不错,为了加深记忆,决定把书中重要的部分整理成读书笔记,便于后期查阅,同时但愿为初学 HBase 的同窗带来一些帮助。算法

目录

本文内容略长,看的时候须要一些耐心。文章首先回顾了 HBase 的数据模型和数据层级结构,对数据的每一个层级的做用和构架均进行了详细阐述;随后介绍了数据写入和读取的详细流程;最后介绍了老版本到新版本 Region 查找的演进。数据库

1、数据模型

一、重要概念回顾

  • Namespace(表命名空间):将多个表分到一个组进行统一管理。
  • Table(表):一个表由一个或者多个列族组成;数据属性好比:超时时间(TTL),压缩算法(COMPRESSION)等,都在列族的定义中定义;定义完列族后表是空的,只有添加了行,表才有数据。
  • Row(行):一个行包含了多个列,这些列经过列族来分类;行中的数据所属列族只能从该表所定义的列族中选取;因为 HBase 是一个列式数据库,因此一个行中的数据能够分布在不一样的服务器上。
  • Column Family(列族):列族是多个列的集合,HBase 会尽可能把同一个列族的列放到同一个服务器上,这样能够提升存取性能,而且能够批量管理有关联的一堆列;全部的数据属性都是定义在列族上;在 HBase 中,建表定义的不是列,而是列族。
  • Column Qualifier(列):多个列组成一个行,列族和列常常用 Column Family: Column Qualifier 来一块儿表示,列是能够随意定义的,一个行中的列不限名字、不限数量。
  • Cell(单元格):一个列中能够存储多个版本的数据,而每一个版本就称为一个单元格(Cell),因此在 HBase 中的单元格跟传统关系型数据库的单元格概念不同;HBase 中的数据细粒度比传统数据结构更细一级,同一个位置的数据还细分红多个版本。
  • Timestamp(时间戳/版本号):既能够把它称为是时间戳,也能够称为是版本号,由于它是用来标定同一个列中多个单元格的版本号的。不指定版本号的时候,系统会自动采用当前的时间戳来做为版本号;当手动定义了一个数字来看成版本号的时候,这个 Timestamp 就真的是只有版本号的意义了。

二、几个小问题

HBase是否支持表关联?

官方给出的答案是干脆的,那就是“不支持”。若是想实现数据之间的关联,就必须本身去实现了,这是挑选 NoSQL 数据库必须付出的代价。编程

HBase 是否支持 ACID?

ACID 就是 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)的首字母缩写,ACID 是事务正确执行的保证,HBase 部分支持 了 ACID。缓存

表命名空间有什么用?

表命名空间主要是用来对表分组,那么对表分组有什么用?命名空间能够填补 HBase 没法在一个实例上分库的缺憾,经过命名空间咱们能够像关系型数据库同样将表分组,对于不一样的组进行不一样的环境设定,好比配额管理、安全管理等。安全

HBase 中有两个保留表空间是预先定义好的:bash

  • hbase:系统表空间,用于组织 HBase 内部表;
  • default:那些没有定义表空间的表都被自动分配到这个表空间下。

2、HBase 的存储数据方式

一、架构回顾

一个 HBase 集群由一个 Master(也能够把两个 Master 作成 HighAvailable)和多个 RegionServer 组成。服务器

  • Master:负责启动的时候分配 Region 到具体的 RegionServer,执行各类管理操做,好比 Region 的分割和合并。HBase 中的 Master 的角色功能比其余类型集群弱不少,HBase 的 Master 很特别,由于数据的读取和写入都跟它没什么关系,它挂了业务系统照样运行。固然 Master 也不能宕机过久,有不少必要的操做,好比建立表、修改列族配置,以及更重要的分割和合并都须要它的操做。
  • RegionServer:RegionServer 上有一个或者多个 Region,咱们读写的数据就存储在 Region 上。若是你的 HBase 是基于 HDFS 的(单机 HBase 可基于本地磁盘),那么 Region 全部数据存取操做都是调用了 HDFS 的客户端接口来实现的。
  • Region:表的一部分数据,HBase 是一个会自动分片的数据库,一个 Region 就至关于关系型数据库中分区表的一个分区,或者 MongoDB 的一个分片。
  • HDFS:HBase 并不直接跟服务器的硬盘交互,而是跟 HDFS 交互,因此 HDFS 是真正承载数据的载体。
  • ZooKeeper:ZooKeeper 在 HBase 中的比 Master 更重要,把 Master 关掉业务系统照样跑,能读能写;可是把 ZooKeeper 关掉,就不能读取数据了,由于读取数据所须要的元数据表 hbase:meata 的位置存储在 ZooKeeper 上。

HBase 的宏观架构

二、RegionServer 内部架构

一个 RegionServer 包含有:数据结构

  • 一个 WAL:预写日志,WAL 是 Write-Ahead Log 的缩写,就是:预先写入。当操做到达 Region 的时候,HBase 先把操做写到 WAL 里面去,HBase 会把数据放到基于内存实现的 Memstore 里,等数据达到必定的数量时才刷写(flush)到最终存储的 HFile 内,而若是在这个过程当中服务器宕机或者断电了,那么数据就丢失了。WAL 是一个保险机制,数据在写到 Memstore 以前,先被写到 WAL 了,这样当故障恢复的时候依旧能够从 WAL 中恢复数据。
  • 多个 Region:Region 至关于一个数据分片,每个 Region 都有起始 rowkey 和结束 rowkey,表明了它所存储的 row 范围。

RegionServer 内部架构

三、Region 内部架构

每个 Region 内都包含有多个 Store 实例,一个 Store 对应一个列族的数据,若是一个表有两个列族,那么在一个 Region 里面就有两个 Store,Store 内部有 MemStore 和 HFile 这两个组成部分。架构

Region 内部架构

四、预写日志(WAL)

预写日志(Write-ahead log,WAL)就是设计来解决宕机以后的操做恢复问题的,数据到达 Region 的时候是先写入 WAL,而后再被加载到 Memstore,就算 Region 的机器宕掉了,因为 WAL 的数据是存储在 HDFS 上的,因此数据并不会丢失。异步

WAL 是默认开启的,能够经过下面的代码关闭 WAL。

Mutation.setDurability(Durability.SKIP_WAL);
复制代码

Put、Append、Increment、Delete 都是 Mutation 的子类,因此他们都有 setDurability 方法,这样可让该数据操做快一点,可是最好不要这样作,由于当服务器宕机时,数据就会丢失。

若是你实在想不惜经过关闭 WAL 来提升性能,能够选择异步写入 WAL

Mutation.setDurability(Durability.ASYNC_WAL);
复制代码

这样设定后 Region 会等到条件知足的时候才把操做写入 WAL,这里提到的条件主要指的是时间间隔 hbase.regionserver.optionallogflushinterval,这个时间间隔的意思是 HBase 间隔多久会把操做从内存写入 WAL,默认值是 1s。

若是你的系统对性能要求极高、对数据一致性要求不高,而且系统的性能瓶颈出如今 WAL 上的时候,你能够考虑使用异步写入 WAL,不然,使用默认的配置便可。

五、WAL 滚动

WAL 是一个环状的滚动日志结构,由于这种结构写入效果最高,并且能够保证空间不会持续变大。

WAL 的检查间隔由 hbase.regionserver.logroll.period 定义,默认值为 1h。检查的内容是把当前 WAL 中的操做跟实际持久化到 HDFS 上的操做比较,看哪些操做已经被持久化了,被持久化的操做就会被移动到 .oldlogs 文件夹内(这个文件夹也是在 HDFS 上的)。

一个 WAL 实例包含有多个 WAL 文件,WAL 文件的最大数量经过 hbase.regionserver.maxlogs(默认是 32)参数来定义。

其余的触发滚动的条件是:

  • 当 WAL 文件所在的块(Block)快要满了;
  • 当WAL所占的空间大于或者等于某个阀值,该阀值的计算公式是:hbase.regionserver.hlog.blocksize * hbase.regionserver.logroll.multiplier
  • hbase.regionserver.hlog.blocksize 是标定存储系统的块(Block)大小的,你若是是基于 HDFS 的,那么只须要把这个值设定成 HDFS 的块大小便可;
  • hbase.regionserver.logroll.multiplier 是一个百分比,默认设定成 0.95,意思是 95%,若是 WAL 文件所占的空间大于或者等于 95% 的块大小,则这个 WAL 文件就会被归档到 .oldlogs 文件夹内。

WAL 文件被建立出来后会放在 /hbase/.log 下(这里说的路径都是基于 HDFS),一旦 WAL 文件被断定为要归档,则会被移动到 /hbase/.oldlogs 文件夹。Master 会负责按期地去清理 .oldlogs 文件夹,判断的条件是“没有任何引用指向这个 WAL 文件”。目前有两种服务可能会引用 WAL 文件:

  • TTL 进程:该进程会保证 WAL 文件一直存活直到达到 hbase.master.logcleaner.ttl 定义的超时时间(默认 10 分钟)为止;
  • 备份(replication)机制:若是开启了 HBase 的备份机制,那么 HBase 要保证备份集群已经彻底不须要这个 WAL 文件了,才会删除这个 WAL 文件。这里提到的 replication 不是文件的备份数,而是 0.90 版本加入的特性,这个特性用于把一个集群的数据实时备份到另一个集群。

六、Store 内部结构

在 Store 中有两个重要组成部分:

  • MemStore:每一个 Store 中有一个 MemStore 实例,数据写入 WAL 以后就会被放入 MemStore。MemStore 是内存的存储对象,只有当 MemStore 满了的时候才会将数据刷写(flush)到 HFile 中;
  • HFile:在 Store 中有多个 HFile,当 MemStore 满了以后 HBase 就会在 HDFS 上生成一个新的 HFile,而后把 MemStore 中的内容写到这个 HFile 中。HFile 直接跟 HDFS 打交道,它是数据的存储实体。

Store 内部结构

WAL 是存储在 HDFS 上的,Memstore 是存储在内存中的,HFile 又是存储在 HDFS 上的;数据是先写入 WAL,再被放入 Memstore,最后被持久化到 HFile 中。数据在进入 HFile 以前已经被存储到 HDFS 一次了,为何还须要被放入 Memstore?

这是由于 HDFS 上的文件只能建立、追加、删除,可是不能修改。对于一个数据库来讲,按顺序地存放数据是很是重要的,这是性能的保障,因此咱们不能按照数据到来的顺序来写入硬盘。

可使用内存先把数据整理成顺序存放,而后再一块儿写入硬盘,这就是 Memstore 存在的意义。虽然 Memstore 是存储在内存中的,HFile 和 WAL 是存储在 HDFS 上的,但因为数据在写入 Memstore 以前,要先被写入 WAL,因此增长 Memstore 的大小并不能加速写入速度。Memstore 存在的意义是维持数据按照 rowkey 顺序排列,而不是作一个缓存。

七、MemStore

设计 MemStore 的缘由有如下几点:

  • 因为 HDFS 上的文件不可修改,为了让数据顺序存储从而提升读取效率,HBase 使用了 LSM 树结构来存储数据,数据会先在 Memstore 中整理成 LSM 树,最后再刷写到 HFile 上。
  • 优化数据的存储,好比一个数据添加后就立刻删除了,这样在刷写的时候就能够直接不把这个数据写到 HDFS 上。

不过不要想固然地认为读取也是先读取 Memstore 再读取磁盘哟!读取的时候是有专门的缓存叫 BlockCache,这个 BlockCache 若是开启了,就是先读 BlockCache,读不到才是读 HFile+Memstore。

八、HFile(StoreFile)

HFile 是数据存储的实际载体,咱们建立的全部表、列等数据都存储在 HFile 里面。HFile 是由一个一个的块组成的,在 HBase 中一个块的大小默认为 64KB,由列族上的 BLOCKSIZE 属性定义。这些块区分了不一样的角色:

  • Data:数据块。每一个 HFile 有多个 Data 块,咱们存储在 HBase 表中的数据就在这里,Data 块实际上是可选的,可是几乎很难看到不包含 Data 块的 HFile;
  • Meta:元数据块。Meta 块是可选的,Meta 块只有在文件关闭的时候才会写入。Meta 块存储了该 HFile 文件的元数据信息,在 v2 以前布隆过滤器(Bloom Filter)的信息直接放在 Meta 里面存储,v2 以后分离出来单独存储;
  • FileInfo:文件信息,其实也是一种数据存储块。FileInfo 是 HFile 的必要组成部分,是必选的,它只有在文件关闭的时候写入,存储的是这个文件的信息,好比最后一个 Key(LastKey),平均的 Key 长度(AvgKeyLen)等;
  • DataIndex:存储 Data 块索引信息的块文件。索引的信息其实也就是 Data 块的偏移值(offset),DataIndex 也是可选的,有 Data 块才有 DataIndex;
  • MetaIndex:存储 Meta 块索引信息的块文件。MetaIndex 块也是可选的,有 Meta 块才有 MetaIndex;
  • Trailer必选的,它存储了 FileInfo、DataIndex、MetaIndex 块的偏移值。

HFile 组成结构

其实叫 HFile 或者 StoreFile 都没错,在物理存储上咱们管 MemStore 刷写而成的文件叫 HFile,StoreFile 就是 HFile 的抽象类而已。

九、Data 数据块

Data 数据块的第一位存储的是块的类型,后面存储的是多个 KeyValue 键值对,也就是单元格(Cell)的实现类,Cell 是一个接口,KeyValue 是它的实现类。

Data 数据块结构

十、KeyValue 类

一个 KeyValue 类里面最后一个部分是存储数据的 Value,而前面的部分都是存储跟该单元格相关的元数据信息。若是你存储的 value 很小,那么这个单元格的绝大部分空间就都是 rowkey、column family、column 等的元数据,因此你们的列族和列的名字若是很长,大部分的空间就都被拿来存储这些数据了。

不过若是采用适当的压缩算法就能够极大地节省存储列族、列等信息的空间了,因此在实际的使用中,能够经过指定压缩算法来压缩这些元数据。不过压缩和解压必然带来性能损耗,因此使用压缩也须要根据实际状况来取舍。若是你的数据主要是归档数据,不太要求读写性能,那么压缩算法就比较适合你。

KeyValue 结构

3、增删查改的真正面目

HBase 是一个能够随机读写的数据库,而它所基于的持久化层 HDFS 倒是要么新增,要么整个删除,不能修改的系统。那 HBase 怎么实现咱们的增删查改的?真实的状况是这样的:HBase 几乎老是在作新增操做。

  • 当你新增一个单元格的时候,HBase 在 HDFS 上新增一条数据;
  • 当你修改一个单元格的时候,HBase 在 HDFS 又新增一条数据,只是版本号比以前那个大(或者你本身定义);
  • 当你删除一个单元格的时候,HBase 仍是新增一条数据!只是这条数据没有 value,类型为 DELETE,这条数据叫墓碑标记(Tombstone)。

因为数据库在使用过程当中积累了不少增删查改操做,数据的连续性和顺序性必然会被破坏。为了提高性能,HBase 每间隔一段时间都会进行一次合并(Compaction),合并的对象为 HFile 文件。

合并分为 minor compaction 和 major compaction,在 HBase 进行 major compaction 的时候,它会把多个 HFile 合并成 1 个 HFile,在这个过程当中,一旦检测到有被打上墓碑标记的记录,在合并的过程当中就忽略这条记录,这样在新产生的 HFile 中,就没有这条记录了,天然也就至关于被真正地删除了。

4、HBase 数据结构总结

HBase 数据的内部结构大致以下:

  • 一个 RegionServer 包含多个 Region,划分规则是:一个表的一段键值在一个 RegionServer 上会产生一个 Region。不过当某一行的数据量太大了(要很是大),HBase 也会把这个 Region 根据列族切分到不一样的机器上去;
  • 一个 Region 包含多个 Store,划分规则是:一个列族分为一个 Store,若是一个表只有一个列族,那么这个表在这个机器上的每个 Region 里面都只有一个 Store;
  • 一个 Store 里面只有一个 Memstore;
  • 一个 Store 里面有多个 HFile,每次 Memstore 的刷写(flush)就产生一个新的 HFile 出来。

数据单元层次图

5、KeyValue 的写入和读出

一、写入

一个 KeyValue 被持久化到 HDFS 的过程的以下:

KeyValue 写入过程

  • WAL:数据被发出以后第一时间被写入 WAL,因为 WAL 是基于 HDFS 来实现的,因此也能够说如今单元格就已经被持久化了,可是 WAL 只是一个暂存的日志,它是不区分 Store 的,这些数据是不能被直接读取和使用;
  • Memstore:数据随后会当即被放入 Memstore 中进行整理,Memstore 会负责按照 LSM 树的结构来存放数据,这个过程就像咱们在打牌的时候,抓牌以后在手上对牌进行整理的过程;
  • HFile:最后,当 Memstore 太大了达到尺寸上的阀值,或者达到了刷写时间间隔阀值的时候,HBaes 会把这个 Memstore 的内容刷写到 HDFS 系统上,称为一个存储在硬盘上的 HFile 文件。至此,咱们能够称为数据真正地被持久化到硬盘上,就算宕机,断电,数据也不会丢失了。

二、读出

因为有 MemStore(基于内存)和 HFile(基于HDFS)这两个机制,你必定会立马想到先读取 MemStore,若是找不到,再去 HFile 中查询。这是显而易见的机制,惋惜 HBase 在处理读取的时候并非这样的。实际的读取顺序是先从 BlockCache 中找数据,找不到了再去 Memstore 和 HFile 中查询数据。

墓碑标记和数据不在一个地方,读取数据的时候怎么知道这个数据要删除呢?若是这个数据比它的墓碑标记更早被读到,那在这个时间点真是不知道这个数据会被删 除,只有当扫描器接着往下读,读到墓碑标记的时候才知道这个数据是被标记为删除的,不须要返回给用户。

因此 HBase 的 Scan 操做在取到所须要的全部行键对应的信息以后还会继续扫描下去,直到被扫描的数据大于给出的限定条件为止,这样它才能知道哪些数据应该被返回给用户,而哪些应该被舍弃。因此你增长过滤条件也没法减小 Scan 遍历的行数,只有缩小 STARTROW 和 ENDROW 之间的行键范围才能够明显地加快扫描的速度

在 Scan 扫描的时候 store 会建立 StoreScanner 实例,StoreScanner 会把 MemStore 和 HFile 结合起来扫描,因此具体从 MemStore 仍是 HFile 中读取数据,外部的调用者都不须要知道具体的细节。当 StoreScanner 打开的时候,会先定位到起始行键(STARTROW)上,而后开始往下扫描。

StoreScanne 扫描数据

其中红色块部分都是属于指定 row 的数据,Scan 要把全部符合条件的 StoreScanner 都扫描过一遍以后才会返回数据给用户。

6、Region 的定位

Region 的查找,早期的设计(0.96.0)以前是被称为三层查询架构:

三层查询架构

  • Region:查找的数据所在的 Region;
  • .META.:是一张元数据表,它存储了全部 Region 的简要信息,.META. 表中的一行记录就是一个 Region,该行记录了该 Region 的起始行、结束行和该 Region 的链接信息,这样客户端就能够经过这个来判断须要的数据在哪一个 Region 上;
  • -ROOT-:是一张存储 .META. 表的表,.META. 能够有不少张,而 -ROOT- 就是存储了 .META. 表在什么 Region 上的信息(.META. 表也是一张普通的表,也在 Region 上)。经过两层的扩展最多能够支持约 171 亿个 Region。

-ROOT- 表记录在 ZooKeeper 上,路径为:/hbase/root-region-server;Client 查找数据的流程从宏观角度来看是这样的:

  • 用户经过查找 zk(ZooKeeper)的 /hbase/root-regionserver 节点来知道 -ROOT- 表在什么 RegionServer 上;
  • 访问 -ROOT- 表,看须要的数据在哪一个 .META. 表上,这个 .META. 表在什么 RegionServer 上;
  • 访问 .META. 表来看要查询的行键在什么 Region 范围里面;
  • 链接具体的数据所在的 RegionServer,这回就真的开始用 Scan 来遍历 row 了。

早期版本 Region 查找过程

从 0.96 版本以后这个三层查询架构被改为了二层查询架构,-ROOT- 表被去掉了,同时 zk 中的 /hbase/root-region-server 也被去掉了,直接把 .META. 表所在的 RegionServer 信息存储到了 zk 中的 /hbase/meta-region-server。再后来引入了 namespace,.META. 表被修改为了 hbase:meta

新版 Region 查找流程:

  • 客户端先经过 ZooKeeper 的 /hbase/meta-region-server 节点查询到哪台 RegionServer 上有 hbase:meta 表。
  • 客户端链接含有 hbase:meta 表的 RegionServer,hbase:meta 表存储了全部 Region 的行键范围信息,经过这个表就能够查询出要存取的 rowkey 属于哪一个 Region 的范围里面,以及这个 Region 又是属于哪一个 RegionServer;
  • 获取这些信息后,客户端就能够直连其中一台拥有要存取的 rowkey 的 RegionServer,并直接对其操做;
  • 客户端会把 meta 信息缓存起来,下次操做就不须要进行以上加载 hbase:meta 的步骤了。

新版 Region 查找过程


Any Code,Code Any!

扫码关注『AnyCode』,编程路上,一块儿前行。

相关文章
相关标签/搜索