HBASE (二)

存储结构

宏观架构

  • Master: 负责启动的时候分配Region到具体的RegionServer, 执行各种管理操作,比如创建表、修改列族配置、Region的分割和合并。
  • RegionServer: 存储Region.
  • Region: HBase是一个会自动分片的数据库。一个Region 相当于关系型数据库中表的一个分区。
  • HDFS: HBase底层的数据载体。
  • Zookeeper:meta的位置存储在zookeeper中

RegionServer结构如下图所示:

预写日志

数据到达Region的时候是先写入WAL,然后再被加载到Memstore的。就算Region的机器宕掉了,由于WAL的数据是存储在HDFS上的,所以数据并不会丢失。

延迟(异步)同步写入WAL

1Mutation.setDurability(Durability.ASYNC_WAL) 2Mutation.setDurability(Durability.SKIP_WAL) 3

WAL滚动

WAL是一个环状的滚动日志结构,因为这种结构写入效果最高,而且可以保证空间不会持续变大。

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

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

WAL文件归档

WAL文件被创建出来后会放在/hbase/.log下(这里说的路径都是基于HDFS),一旦WAL文件被判定为要归档,则会被移动
到/hbase/.oldlogs文件夹

Master会负责定期地去清理.oldlogs文件夹,条件是“当这个WAL不需要作为用来恢复数据的备份”的时候。

  • TTL进程:该进程会保证WAL文件一直存活直到达到hbase.master.logcleaner.ttl定义的超时时间(默认10分钟)为止
  • 备份(replication)机制:如果开启备份机制,那么HBase要保证备份集群已经完全不需要这个WAL文件了,才会删

除这个WAL文件

MemStore

数据被写入WAL之后就会被加载到MemStore中去。MemStore的大小增加到超过一定阀值的时候就会被刷写到HDFS上,以HFile的形式被持久化起来。

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

在LSM树的实现方式中,有一个必经的步骤,那就是在数据存储之前先对数据进行排序。而LSM树也是保证HBase能稳定地提供高性能的读能力的基本算法。

HFile

HFile是数据存储的实际载体,我们创建的所有表、列等数据都存储在HFile里面。结构如下图所示:

我们可以看到HFile是由一个一个的块组成的。在HBase中一个块的大小默认为64KB,由列族上的BLOCKSIZE属性定义。这些块区分了不同的角色:

  • Data: 数据块。我们存储在HBase表中的数据就在这里
  • Meta:元数据块。Meta块是可选的,Meta块只有在文件关闭的时候才会写入。Meta块存储了该HFile文件的元数据信息
  • FileInfo:文件信息,其实也是一种数据存储块。FileInfo是HFile的必要组成部分,是必选的。它只有在文件关闭的时候写

入,存储的是这个文件的信息,比如最后一个Key(LastKey),平均的Key长度(Avg Key Len)等。

  • DataIndex:存储Data块索引信息的块文件。索引的信息其实也就是Data块的偏移值(offset)。DataIndex也是可选的,有

Data块才有DataIndex

  • MetaIndex:存储Meta块索引信息的块文件
  • Trailer:必选的,它存储了FileInfo、DataIndex、MetaIndex块的偏移值。

Data数据块

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

BlockType(块类型)

块类型包括:

  • DATA
  • ENCODED_DATA
  • LEAF_INDEX
  • BLOOM_CHUNK
  • META
  • INTERMEDIATE_INDEX
  • ROOT_INDEX
  • FILE_INFO
  • GENERAL_BLOOM_META
  • DELETE_FAMILY_BLOOM_META
  • TRAILER
  • INDEX_V1

StoreFile还是HFile

在物理存储上我们管MemStore刷写而成的文件叫HFile,StoreFile就是HFile的抽象类而已

KeyValue类

单元格最重要的实现类KeyValue类的结构如上图所示。

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

可以通过指定压缩算法来压缩这些元数据。不过压缩和解压必然带来性能损耗。

数据单元层次图

归纳起来如下:

  • 一个RegionServer包含多个Region,划分规则是:一个表的一段键值在一个RegionServer上会产生一个Region。不过当你1行的数据量太大了(要非常大,否则默认都是不切分的),HBase也会把你的这个Region根据列族切分到不同的机器上去
  • 一个Region包含多个Store,划分规则是:一个列族分为一个Store,如果一个表只有一个列族,那么这个表在这个机器上的

每一个Region里面都只有一个Store。

  • 一个Store里面只有一个Memstore。
  • 一个Store里面有多个HFile(StoreFile是HFile的抽象对象,所以如果说到StoreFile就等于HFile)。每次Memstore的刷写(flush)就产生一个新的HFile出来。

数据读取

实际的读取顺序是先从BlockCache中找数据,找不到了再去Memstore和HFile中查询数据

由于HDFS的文件不可变特性,你不可能在一个KeyValue被新建之后删除它,HBase所能做的也就是帮你加上一个墓碑标记。

墓碑标记和数据不在一个地方,HBase的Scan操作在取到所需要的所有行键对应的信息之后还会继续扫描下去,直到被扫描的数据大于给出的限定条件为止,这样它才能知道哪些数据应该被返回给用户,而哪些应该被舍弃。所以你增加过滤条件也无法减少Scan遍历的行数,只有缩小STARTROW和ENDROW之间的行键范围才可以明显地加快扫描的速度。

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

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

Region的定位

  1. 客户端先通过ZooKeeper的/hbase/meta-region-server节点查询到哪台RegionServer上有hbase:meta表。
  2. 客户端连接含有hbase:meta表的RegionServer。hbase:meta表存储了所有Region的行键范围信息,通过这个表就可以查询出你要存取的rowkey属于哪个Region的范围里面,以及这个Region又是属于哪个RegionServer。
  3. 获取这些信息后,客户端就可以直连其中一台拥有你要存取的rowkey的RegionServer,并直接对其操作
  4. 客户端会把meta信息缓存起来,下次操作就不需要进行以上加载hbase:meta的步骤了

代码交流 2021