【编者按】HBase,Google BigTable的开源实现,也是Hadoop的原生组件之一。基于HDFS,HBase提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统。虽然有着高贵的血统,但是HBase中存在的问题也不容忽视,比如前文所说的一些工程问题。近日@DataScientist对HBase在2013年的发展进行了回顾,总结了其中最值得关注的几个方面,并分享了一些实践经验。以下为原文: 2013年马上就要过去了,总结下这一年HBase在这么一年中发生的主要变化。影响最大的事件就是HBase 0.96的发布,代码结构已经按照模块化发布了,而且提供了许多大家迫切需求的特点。这些特点大多在Yahoo!/Facebook/淘宝/小米等公司内 部的集群中跑了挺长时间了,可以算是比较稳定可用了。 1. Compaction优化HBase的Compaction是长期以来广受诟病的一个特性,很多人吐槽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的读写性能。 更多内容:HBase Compaction、HBase 2013 Compaction 提升。 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是目前看到的最好的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也实现了一大一小两个表的Join操作。还是老办法把小表broadcast到所有的RS,然后通过coprocessor来做hash join,最后汇总。感觉有点画蛇添足,毕竟HBase设计的初衷就是用大表数据冗余来尽量避免Join操作的。现在又来支持Join,不知道Salesfore的什么业务需求这个场景。 关于Transaction的支持,目前最受关注的还就是Yahoo!的Omid。不过貌似大家对这个特性的热情还不是特别高。 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。但是这个特性也仅仅是通过delta encoding/compression降低了内存占用率,对数据查询效率没有提升,甚至会带来压缩/解压缩对CPU资源占用的情况。 HBASE-4676:PrefixTreeCompression是把重复的Row/Family/Qualifier按照Prefix Tree的形式进行压缩存储的,可以在解析时生成前缀树,并且树节点的儿子是排序的,所以从DataBlock中查询数据的效率可以超过二分查找。(PREFIX_TREE压缩的初步探究及测试) 7. 其他变化
展望2014年,HBase即将release 1.0版本,更好的支持multi-tenancy, 支持Cell级别的ACL控制。 8. 总结
原文链接: HBase in 2013 |