2014年6月18日星期三

[翻译整理]Apache HBase - HFile

简介

Apache HBase是Hadoop的分布式开源的存储管理工具,非常适合随机实时的io操作。
我们知道,Hadoop的Sequence File是一个顺序读写,批量处理的系统。但是为什么HBase能做到随机的,实时的io操作呢?
Hadoop底层使用Sequence File文件格式存储,Sequence File允许以追加的方式增加k-v数据,根据hdfs的append-only的特性,Sequence File不允许修改或删除一个指定的数据。只有append操作是被允许的,而且你想要查找某个key,你只能遍历文件,知道找到这个key为止。
所以,在这个文件格式至上搭建的HBase系统,是如何搭建随机读写,低访问延迟的呢?
上文中提到了MapFile,是一个以Sequence File为基础的文件格式。MapFile实际上是由两个Sequence File组成的,/data存储了数据,/index存储了索引。
MapFile提供了一个按顺序存储的方式,每当N(N是可配置的)条记录写入后,会将文件偏移地址写入index文件;这就实现了快速查找,相比直接遍历Sequence File,你可以直接遍历index文件,index文件存储了更少的文件句柄,一旦找到了文件块所在的位置,可以直接跳到该文件块查找;因此查找速度非常快。
但是还有另外两个问题:
1. 如何删除或更新一个k-v记录;
2. 当插入数据不是有序的,如何使用MapFile

HBase&MapFile


HBase的key包括:行键,列族,column qualifier,时间戳,属性。
为了解决删除的问题,使用key中的type标记该记录是否被删除。
解决更新的问题,实际上是获取时间戳更晚的记录,正确的数据总会更接近文件的末尾。
为了解决无序数据的问题,hbase将插入的数据暂存在内存中,直到一个阈值到达,hbase会将内存中的数据存储到MapFile中。
hbase在内存中使用sorted ConcurrentSkipListMap数据结构存储数据,每次到达阈值(hbase.hregion.memstore.flush.size)或到达内存上限(hbase.regionserver.global.memstore.upperLimit),都会将内存中的数据向一个新的mapFile写入。
由于每次flush操作都会写入一个新的MapFile,这就意味着查找时,会横跨多个文件,这也会耗费更多的资源和时间。
为了避免在查找get和scan扫描时横跨过多的文件,hbase中有一个线程来执行文件合并的相关操作。当线程发现文件数量达到阈值(hbase.hstore.compaction.max)后,会有一个compaction进程来执行文件合并,将小的文件合并到一个大文件中。
hbase中有两种合并模式,minor和major。minor会合并两个或多个较小的文件到一个大文件中。另外的major会合并所有的文件到一个文件中,并且执行一些清理,被删除的数据将不会被写入新的文件,并且重复的数据会被清除,只留下最新的有效数据。
0.20以下版本的hbase使用上述的文件存储方式,到0.20以后,hfile v1被引入替代了MapFile。

HFile v1

在HBase 0.20版本中,一个新的文件存储格式被引入到hbase中-hfile。hfile和mapfile非常类似,但是一些新的特性被添加进来。例如支持元数据,index也被保存到同一个文件中。

数据块(data blocks)中存储key-values,可以看做是一个MapFile。
当block关闭操作时,第一个key会被写入index中,index文件在hfile关闭操作时写入。
Hfile还增加两个额外的元数据类型,Meta和FileInfo。这两个数据也是在hfile的close时被写入的。
HBase的Regionserver的存储文件中,使用Meta Block存储了BloomFilter,使用FileInfo存储了最大的SequenceId,Major compaction key and 时间跨度信息。
这些信息在旧文件或非常新的文件中判断key是否存在非常有用。
关于BloomFilter,是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。

HFile v2

在hbase 0.92版本中,为了改进在大数据存储下的效率,HFile做了改变。
hfile v1的主要问题是,你需要load所有的单片索引和BloomFilter到内存中;
为了解决这个问题,v2引入了多级索引和分块BloomFilter;HFile v2改进了速度,内存和缓存利用率。
v2的最主要特性是内联块。主要思想是拆分了索引和BloomFilter到每个数据块中,来解决整个文件的索引和BloomFilter都load到内存中的问题;
因为索引被拆分到每个数据块中,这就意味着每个数据块都有自己的索引(leaf-index);每个数据块中的最后一个key被当做节点组建了类似b+树的多级索引结构;
数据块的头信息中的Block Magic被Block Type替换,Block Type包含描述Block数据的一些信息,包括叶子索引,Bloom,元数据,根索引等。
其他三个字段(compressed/uncompressed size and offset prev block)也被添加用于前后的快速查找;

  • BlockType:8字节长度(long),支持的类型包括
    • 数据块(data blocks)
    • LEAF_INDEX
    • BLOOM_CHUNK
    • META
    • INTERMEDIATE_INDEX
    • ROOT_INDEX
    • FILE_INFO
    • BLOOM_META
    • TRAILER
    • INDEX_V1
  • 压缩大小,不包括header。可以在扫描的时候略过本块数据;
  • 非压缩大小,当压缩算法为NON的时候,和压缩大小相同
  • 同一类型上一块的block偏移地址,可以帮助搜索上一数据块/索引块的地址;
  • 压缩的数据(如果压缩算法为NON,则是非压缩数据)

DataBlock编码


因为大部分的key都非常类似,因此要设计一个良好的压缩方式;HBase0.94开始可以在前缀编码和diff编码做选择;
前缀编码的特点是相同的前缀只存储一次;
Diff编码把前缀编码做到了极致;相同的项将会被省略存储,例如key len,如果两个key len相同,第二个将被省略;而对于递增的时间戳,只存储了相对于上一个时间戳的变化量;
注意,这样的存储格式是默认关闭的,因为将会导致查询变慢,但是更多的数据可以加载到cache中。通过修改表的ATA_BLOCK_ENCODING = PREFIX | DIFF | FAST_DIFF 来设置使用的存储方式;

HFile v3

 HBASE-5313包含了一个重新变更HFile格式以提高数据压缩率的提议。
原文地址:http://blog.cloudera.com/blog/2012/06/hbase-io-hfile-input-output/

没有评论:

发表评论