2013年马上就要过去了,总结下这一年HBase在这么一年中发生的主要变化。影响最大的事件就是HBase 0.96的发布,代码结构已经按照模块化release了,而且提供了许多大家迫切需求的特点。这些特点大多在Yahoo/Facebook/淘宝/小米等公司内部的集群中跑了挺长时间了,可以算是比较稳定可用了。
1,Compaction优化
HBase的Compaction是长期以来广受诟病的一个feature,很多人吐槽HBase也是因为这个特征。不过我们不能因为HBase有这样一个缺点就把它一棒子打死,更多的还是希望能够驯服它,能够使得它适应自己的应用场景。根据业务负载类型调整compaction的类型和参数,一般在业务高峰时候禁掉Major Compaction。在0.96中HBase社区为了提供更多的compaction的策略适用于不同的应用场景,采用了插件式的架构。同时改进了HBase在RegionServer端的存储管理,原来是直接Region->Store->StoreFile,现在为了支持更加灵活多样的管理StoreFile和compact的策略,RS端采用了StoreEngine的结构。一个StoreEngine涉及到StoreFlusher, CompactionPolicy, Compactor, StoreFileManager。不指定的话默认是DefaultStoreEngine,四个组件分别是DefaultStoreFlusher, ExploringCompactionPolicy, DefaultCompactor, DefaultStoreFileManager。可以看出在0.96版之后,默认的Compaction算法从RatioBasedCompactionPolicy改为了ExploringCompactionPolicy。为什么要这么改,首先从compaction的优化目标来看:compaction is about trading some disk IO now for fewer seeks later,也就是compaction的优化目标是执行Compaction操作能合并越多的文件越好,如果合并同样多的文件产生的IO越小越好,这样select出来的列表才是最优的。
主要不同在于:
关于这两个算法的逻辑可以在代码中参考对应的applyCompactionPolicy()函数。其他CompactionPolicy的研究和开发也非常活跃,例如Tier-based compaction (HBASE-6371,来自Facebook) 和stripe compaction(HBASE-7667)
吐槽:HBase compaction为什么会问题这么多,我感觉缺少了一个整体的IO负载的反馈和调度机制。因为compaction是从HDFS读数据,然后再写到HDFS中,和其他HDFS上的负载一样在抢占IO资源。如果能有个IO资源管理和调度的机制,在HDFS负载轻的时候执行compaction,在负载重的时候不要执行。而且这个问题在Hadoop/HDFS里同样存在,Hadoop的资源管理目前只针对CPU/Memory的资源管理,没有对IO的资源管理,会导致有些Job受自己程序bug的影响可能会写大量的数据到HDFS,会严重影响其他正常Job的读写性能。
http://blog.cloudera.com/blog/2013/12/what-are-hbase-compactions/
http://www.slideshare.net/cloudera/hbasecon-2013-compaction-improvements-in-apache-hbase
2,Mean Time To Recovery/MTTR优化
目前HBase对外提供服务,Region Server是单点。如果某台RS挂掉,那么直到该RS上的所有Region被重新分配到其他RS上之前,这些Region是的数据是无法访问的。对这个过程的改进主要包括:
3,Bucket Cache (L2 cache on HBase)
HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。Block的cache命中率对HBase的读性能影响十分大。目前默认的是LruBlockCache,直接使用JVM的HashMap来管理BlockCache,会有Heap碎片和Full GC的问题。
HBASE-7404引入Bucket Cache的概念可以放在内存中,也可以放在像SSD这样的适合高速随机读的外存储设备上,这样使得缓存的空间可以非常大,可以显著提高HBase读性能。Bucket Cache的本质是让HBase自己来管理内存资源而不是让Java的GC来管理,这个特点也是HBase自从诞生以来一直在激烈讨论的问题。
4,Java GC改进
MemStore-Local Allocation Buffers通过预先分配内存块的方式解决了因为内存碎片造成的Full GC问题,但是对于频繁更新操作的时候,MemStore被flush到文件系统时没有reference的chunk还是会触发很多的Young GC。所以HBase-8163提出了MemStoreChunkPool的概念,也就是由HBase来管理一个ChunkPool用来存放chunk,不再依赖JVM的GC。这个ticket的本质也是由HBase进程来管理内存分配和重分配,不再依赖于Java GC。
5,HBase的企业级数据库特性(Secondary Index, Join和Transaction)
谈到HBase的企业级数据库特性,首先想到的就是Secondary Index, Join, Transaction。不过目前这些功能的实现都是通过外围项目的形式提供的。
华为的hindex (https://github.com/Huawei-Hadoop/hindex)是目前看到的最好的Secondary Index的实现方式。主要思想是建立Index Table,而且这个Index Table的Region分布跟主表是一致的,也就是说主表中某一Region对应的Index Table对应的Region是在同一个RS上的。而且这个索引表是禁止自动或者手动出发split的,只有主表发生了split才会触发索引表的split。
这个是怎么做到的呢?本质上Index Table也是一个HBase的表,那么也只有一个RowKey是可以索引的。这个索引表的RowKey设计就比较重要了,索引表的RowKey=主表Region开始的RowKey+索引名(因为一个主表可能有多个索引,都放在同一个索引表中)+需要索引的列值+主表RowKey。这样的索引表的RowKey设计就可以保证索引表和主表对应的Region是在同一台RS上,可以省查询过程中的RPC。每次Insert数据的时候,通过Coprocessor顺便插入到索引表中。每次按照二级索引列Scan数据的时候,先通过Coprocessor从索引表中获取对应的主表的RowKey然后就行Scan。在性能上看,查询的性能获得了极大提升,插入性能下降了10%左右。
Phoenix也通过另外建一张表的方式实现二级索引。https://github.com/forcedotcom/phoenix/wiki/Secondary-Indexing
Phoenix也实现了一大一小两个表的Join操作。还是老办法把小表broadcast到所有的RS,然后通过coprocessor来做hash join,最后汇总。感觉有点画蛇添足,毕竟HBase设计的初衷就是用大表数据冗余来尽量避免Join操作的。现在又来支持Join,不知道Salesfore的什么业务需求这个场景。
关于Transaction的支持,目前最受关注的还就是Yahoo的Omid (https://github.com/yahoo/omid/)。不过貌似大家对这个feature的热情还不是特别高。
http://sigops.org/sosp/sosp11/posters/summaries/sosp11-final12.pdf
6,PrefixTreeCompression
由于HBase的KeyValue存储是按照Row/Family/Qualifier/TimeStamp/Value的形式存储的,Row/Family/Qualifier这些相当于前缀,如果每一行都按照原始数据进行存储会导致占据存储空间比较大。HBase 0.94版本就已经引入了DataBlock Encode的概念(HBASE-4218),将重复的Row/Family/Qualifier按照顺序进行压缩存储,提高内存利用率,支持四种压缩方式FAST_DIFF\PREFIX\PREFIX_TRIE\DIFF。但是这个feature也仅仅是通过delta encoding/compression降低了内存占用率,对数据查询效率没有提升,甚至会带来压缩/解压缩对CPU资源占用的情况。
HBASE-4676:PrefixTreeCompression是把重复的Row/Family/Qualifier按照Prefix Tree的形式进行压缩存储的,可以在解析时生成前缀树,并且树节点的儿子是排序的,所以从DataBlock中查询数据的效率可以超过二分查找。
http://zjushch.iteye.com/blog/1843793
7,其他变化
展望2014年,HBase即将release 1.0版本,更好的支持multi-tenancy, 支持Cell级别的ACL控制。
8,总结