概述
HBase是基于Bigtable论文的面向列的分布式存储系统,其存储设计是基于Memtable/SSTable的。 其它如 Cassandra 都是采用的该设计。
整个存储分为两部分,一部分为内存中的 MemStore(Memtable) ,另外一部分为磁盘 ( 这里是 HDFS) 上的 HFile(SSTable) 。下面分别讲述两种类型的存储分别的实现:
MemStore 中最重要的变量是:
volatile KeyValueSkipListSet kvset;
这里的 KeyValueSkipListSet 里面实际是这样的:
private final ConcurrentNavigableMap<KeyValue, KeyValue> delegatee;
换句话说,其实就是一个放内存的 Map 存放着 kv 。
HFile 是 HBase 实际的文件存储格式,它是基于 TFile 的文件格式,替换了早期的 MapFile ,改进了性能。
然后 HBase 会进行控制,当 MemStore 写满了以后进行刷磁盘操作。
而HLog是HBase的日志格式实现,主要是在写入的时候进行write-ahead-log,主要为recovery而做。
HFile
HFile 是 HBase 中实际存数据的文件,为 HBase 提供高效快速的数据访问。它是基于 Hadoop 的 TFile ,模仿 Google Bigtable 架构中的 SSTable 格式。之前的 Hadoop 的 MapFiles 已经被证明性能不能到达我们的期望。文件格式如下:
文件是变长的,唯一固定的块是 File info 和 Trailer ,如图所示, Trailer 有指向其它块的指针,这些指针也写在了文件里, Index 块记录了 data 和 meta 块的偏移量, data 和 meta 块都是可选的。
块的大小是由表创建时的 HColumnDescriptor 指定的,如下是 master web interface 上看到的一个例子:
{NAME => 'docs', FAMILIES => [{NAME => 'cache', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'false'}, {NAME => 'contents', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'false'}, ...
默认的大小是 64KB ,下面是一段关于 HFile 的解释:
“最小的块大小。我们建议通常将其设置为 8KB 到 1MB 之间,如果经常进行基于 primary key 的顺序访问,可以设置更大的块大小,但是这样会带来低效的随机访问效率(更多的数据需要被解压缩)。更小的块大小将带来更好的随机访问效率,但是会耗费更多的内存去维持索引,并且在创建的时候会比较慢,因为需要 flush 压缩流,这将导致一个 FS I/O flush 。由于内部的压缩 codec 的缓存,最小的块大小可以为 20-30KB ”
上图是 HFile 中每个 KeyValue 的格式,和普通的 key-value 没有太大的区别。
HLog
源码中的实现类是 HLog 。每个 HRegionServer 会对应一个 HLog , HRegion 在初始化的时候 HRegionServer 会将该 HLog 的实例作为构造函数传入其中。 HLog 的核心函数是其 append() 函数。
在 HLog 中,会维持一个 Sequence Number ,是一个 AtomicLong 型,当一个 Region 启动的时候会从 HFile 的 Meta field 里面读出当前的 Sequence Number 。
如图所示,由于 HLog 是 Region 之间共享的,因此, log 的顺序是不定的,这一点后面会提到。当一个 HRegionServer 崩溃掉后, HMaster 会让 HRegionServer 重启的时候根据日志进行恢复。
当前的 WAL 使用的是 Hadoop 下的 SequenceFile 格式,其 key 是 HLogKey 实例,它包括以下内容:
private byte [] encodedRegionName; private byte [] tablename; private long logSeqNum; // Time at which this edit was written. private long writeTime; private byte clusterId;
由于操作系统处理批量的数据要块过单个单个的处理,因此,需要进行 flush 日志。 LogFlusher 实现了该功能,它调用了 HLog.optionalSync() ,它将检查是否到了 hbase.regionserver.optionallogflushinterval ,默认是 10 秒。
日志的大小会有一个限制,这是用 hbase.regionserver.logroll.period 参数控制的,默认是 1 个小时。到点以后 LogRoller 会触发操作,检查当前的 Sequence Number ,看小于它的所有日志是否完整。
参考文献
http://www.larsgeorge.com/
主要参考自larsgeorge的hbase系列文章