由于一直在作hbase的应用层面的开发,因此体会的比较深的一点是hbase的表结构设计会对系统的性能以及开销上形成很大的区别,本篇文章先按照hbase表中的rowkey、columnfamily、column、timestamp几个方面进行一些分析。最后结合分析如何设计一种适合应用的高效表结构。redis
一、表的属性算法
(1)最大版本数:一般是3,若是对于更新比较频繁的应用彻底能够设置为1,可以快速的淘汰无用数据,对于节省存储空间和提升查询速度有效果。不过这类需求在海量数据领域比较小众。数据库
(2)压缩算法:能够尝试一下最新出炉的snappy算法,相对lzo来讲,压缩率接近,压缩效率稍高,解压效率高不少。数据结构
(3)inmemory:表在内存中存放,一直会被忽略的属性。若是彻底将数据存放在内存中,那么hbase和如今流行的内存数据库memorycached和redis性能差距有多少,尚待实测。app
(4)bloomfilter:根据应用来定,看须要精确到rowkey仍是column。不过这里须要理解一下原理,bloomfilter的做用是对一个region下查找记录所在的hfile有用。即若是一个region下的hfile数量不少,bloomfilter的做用越明显。适合那种compaction赶不上flush速度的应用。ide
二、rowkeyoop
2.1 排序问题性能
数字rowkey的从大到小排序:原生hbase只支持从小到大的排序,这样就对于排行榜一类的查询需求很尴尬。那么采用rowkey = Integer.MAX_VALUE-rowkey的方式将rowkey进行转换,最大的变最小,最小的变最大。在应用层再转回来便可完成排序需求。测试
2.2 热点问题
HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操做,能够将相关的行以及会被一块儿读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问多是读,写或者其余操做)。大量访问会使热点region所在的单个机器超出自身承受能力,引发性能降低甚至region不可用,这也会影响同一个RegionServer上的其余region,因为主机没法服务其余region的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。优化
为了不写热点,设计rowkey使得不一样行在同一个region,可是在更多数据状况下,数据应该被写入集群的多个region,而不是一个。
下面是一些常见的避免热点的方法以及它们的优缺点:
加盐
这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增长随机数,具体就是给rowkey分配一个随机前缀以使得它和以前的rowkey的开头不一样。分配的前缀种类数量应该和你想使用数据分散到不一样的region的数量一致。加盐以后的rowkey就会根据随机生成的前缀分散到各个region上,以免热点。
哈希
哈希会使同一行永远用一个前缀加盐。哈希也可使负载分散到整个集群,可是读倒是能够预测的。使用肯定的哈希可让客户端重构完整的rowkey,可使用get操做准确获取某一个行数据
反转
第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可使得rowkey中常常改变的部分(最没有意义的部分)放在前面。这样能够有效的随机rowkey,可是牺牲了rowkey的有序性。
反转rowkey的例子以手机号为rowkey,能够将手机号反转后的字符串做为rowkey,这样的就避免了以手机号那样比较固定开头致使热点问题
时间戳反转
一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳做为rowkey的一部分对这个问题十分有用,能够用 Long.Max_Value - timestamp 追加到key的末尾,例如 [key][reverse_timestamp] , [key] 的最新值能够经过scan [key]得到[key]的第一条记录,由于HBase中rowkey是有序的,第一条记录是最后录入的数据。好比须要保存一个用户的操做记录,按照操做时间倒序排序,在设计rowkey的时候,能够这样设计[userId反转][Long.Max_Value - timestamp],在查询用户的全部操做记录数据的时候,直接指定反转后的userId,startRow是[userId反转][000000000000],stopRow是[userId反转][Long.Max_Value - timestamp]若是须要查询某段时间的操做记录,startRow是[user反转][Long.Max_Value - 起始时间],stopRow是[userId反转][Long.Max_Value - 结束时间] rowkey是hbase的key-value存储中的key,一般使用用户要查询的字段做为rowkey,查询结果做为value。能够经过设计知足几种不一样的查询需求。
三、columnfamily
columnfamily尽可能少,缘由是过多的columnfamily之间会互相影响。
四、column
对于column须要扩展的应用,column能够按普通的方式设计,可是对于列相对固定的应用,最好采用将一行记录封装到一个column中的方式,这样可以节省存储空间。封装的方式推荐protocolbuffer。
如下会分场景介绍一些特殊的表结构设计方法,只是一些摸索,欢迎讨论:
value数目过多场景下的表结构设计:
目前我碰到了一种key-value的数据结构,某一个key下面包含的column不少,以至于客户端查询的时候oom,bulkload写入的时候oom,regionsplit的时候失败这三种后果。一般来说,hbase的column数目不要超过百万这个数量级。在官方的说明和我实际的测试中都验证了这一点。
有两种思路能够参考,第一种是单独处理这些特殊的rowkey,第二种以下:
能够考虑将column设计到rowkey的方法解决。例如原来的rowkey是uid1,,column是uid2,uid3...。从新设计以后rowkey为<uid1>~<uid2>,<uid1>~<uid3>...固然你们会有疑问,这种方式如何查询,若是要查询uid1下面的全部uid怎么办。这里说明一下hbase并非只有get一种随机读取的方法。而是含有scan(startkey,endkey)的扫描方法,而这种方法和get的效率至关。须要取得uid1下的记录只须要new Scan("uid1~","uid1~~")便可。
这里的设计灵感来自于hadoop world大会上的一篇文章,这篇文章自己也很棒,推荐你们看一下http://www.cloudera.com/resource/hadoop-world-2011-presentation-slides-advanced-hbase-schema-design/
拓展阅读:
HBase 在淘宝的应用和优化 http://www.iteye.com/magazines/83