HBase(十二) 最佳实践

举报
大数据小粉 发表于 2016/11/16 08:52:35 2016/11/16
【摘要】 HBase最佳实践

最佳实践


Schema设计

目标
读数据

存储上是否连续分布。上文说过,Hbase读数据实际上都是scan,连续分布的数据读取效率较高,系统IO的垃圾数据较少

写数据

1. 一次RPC能够写入数据量。IO是hbase的瓶颈之一,需要考虑一次RPC能够写入更多的数据。
2. 写数据是否均衡。一段时间之内写入的数据在分布上能够一定程度上分散开能够保证并发度,提高写数据速度。(所以这里和读的顺序性要求是要做权衡的)
3. 写数据是否相对集中。短时间内写入的数据太分散的话会导致容易达到写操作内存警戒线,影响性能。

其他

1. 有效清理数据。Hbase没有提供高效的数据清理手段,数据需要合理的清理方案(Delete/dorptable/TTL/Versions)
2. 保证没有大量过小或者空的region。空region在HBase中占用负载和内存,而且hbase没有有效手段来清理或者合并这些region

表设计
按周期建表

仿照oracle分区表的概念,按周期建表,如按天建表。
这样涉及同一段时间的数据相对集中,且过期清理数据可以简单drop表即可。

一般不使用TTL

TTL要在合并才删除,占用数据空间,且增加compact负担

versions适合少量版本管理

少量的数据版本可以巧用这个功能,但是大批量的版本差异,不太适合用这个功能。
且要注意version类似日志滚动,设置数量是恒定的,新版本进来,最老版本会移除。

纵表比横表性能更好

hbase只能依据行分片,且从查询效率来讲,最有效的信息就是行健,所以把关键信息尽量放到行健中。(要平衡信息与长度)

Bloom filter按需启用

列族可以配置是否开启布隆过滤器。写入数据的时候,要将该数据的key添加到bloomfilter中;读取数据之前,要使用该数据的key在bloomfilter中进行测试,确定该key是否添加到了bloomfilter中。因此,bloomfilter可以用来在读取一个精确的key之前,预判该key是否在文件中实际存在,进而减少读取文件的消耗。由于bloomfilter是根据多个hash函数hash出来的bit值进行判断,可能会有重复,因此bloomfilter可以准确判断出一个key的不存在,而不能准确判断出key的存在。
所以对于单个get或者需要修改所有记录的情况,是不适合使用布隆过滤器的。因为布隆过滤器是有额外的开销的,bloomfilter的vectorsize、元素个数、hash函数个数之间的关系如下:
    M表示vectorsize,n表示元素个数,k表示hash函数个数,则:
    M = (k*n)/ln2
    在hbase中,hash函数个数默认为4,因此,m和n的关系为:
    M=5.77n。
因此,假定有1M条记录要插入bloomfilter,则需要的向量的大小为5.7M bit,约0.7Mb。
如果每条记录在磁盘上存储的大小是256个字节,如果有1T的数据,则有4G条记录,约耗费2.7G内存用于存储bloomfilter。
如果每条记录在磁盘上的存储大小为2K,如果有1T的数据,则有500M条记录,约耗费350M内存用于存储bloomfilter。
因此,存储的每条记录越大,bloomfilter的空间利用效率越高。

所以,适合使用bloomfilter的场景需要同时满足如下条件:
访问数据使用精确的key。
数据是小key,大value的模式。(即使这样,也要根据数据量来计算消耗的内存,综合判断)
尽量使用行级,发现没有性能提升才考虑行+列级的布隆过滤器。

列族和列的设计
family尽量少而不变,可变属性移到Column

可枚举数量少扩展性弱的属性作为Family,不可枚举数量多扩展性强的属性作为column。
Column Family的增、删、改都需要首先disable Table,影响其他Family的正常访问,因此作为Family的属性取值范围要尽量减少扩展。存储角度,一个列族的数据会存在一个store file中,所以列族中数据尽量相关,集中。

属性要求不同的数据按需分开到不同table

每个family有自己独立的属性,例如version和TTL。因此,如果数据对于这些属性需求不同的话,不能存储在同个family中。 有两种选择,存在不同family或不同的table。
放在不同的family中,会导致同一个reagion不同family的大小差异比较大,出现很多小的或者空的store,导致store数量无端增加。由于hbase中,很多内部操作(如flush、compact)的执行单位是store,store的增加会影响系统性能,因此需要尽量避免由于不同family大小失衡导致的大量小store的出现。因此,daily、hourly、monthly数据推荐存放在不同的table。

同时存取的数据序列化后放到一列中

类似一个对象不同属性,同时存取,异或一个请求的不同字段等情况,适合做对象序列化后存入,如PB等。这样减少key的存放,节约存储效率。同时也节省RPC调用。

同时存取的大量数据适合放在同一列族的不同列

数据同时存取,但是量大,就不适合放在一个cell了。这时可以选择分片到同列族的不同列中。这也会减少一定的RPC。

行健设计

从上面目标中已经看出,行健设计要权衡读和写的性能均衡。
因为hbase的查询机制是指定头尾的scan,所以尽可能要保持记录连续性。且要考虑行健设计给分页查询带来的影响,是否能有效识别offset,和offset的跳转。
而连续写恰恰会造成IO热点,会降低写入的性能。但是又不能过于分散,否则同样会造成IO问题。
行健在不过长的前提下,尽量包含关键信息,有几个策略,以下策略是写的效率越来越低,读的效率越来越高:

完全随机

行健完全随机,如行健采用时间戳的md5值。写的效率非常高,但是只适合单行get,任何范围scan都会比其他策略的效率低。

业务可枚举/权重高的字段调整到前面

单业务查询下,权重最高的字段放在最前面,效率最高。
多业务共享表的情况,要综合多业务的实际情况,选一个最优的策略。
可枚举的字段相当于聚合顺序数据,对相关业务查询会提升效率。

加salting前缀

如prefix=(时间戳%region数),这样保证数据分散到所有region server。
好处是可以多线程读取数据,相当于数据平均分割。
坏处是要扫描连续数据时,对每个region server都要发起请求。

顺序行健

所有行健长度相等,严格按照一定顺序增长。这种策略读的效率虽高,但写入效率最低。

循环key+TTL vs 按周期建表

这两种都是数据清理的可能策略。循环key可以保证TTL清除老数据,不会导致空region的问题,会重复写入数据。
按照时间周期进行建表的方式也可以解决空region的问题,和循环key方法相比较,

循环key的优点:

操作简单,不需要重复建表,系统自动处理

循环key劣势:

需要使用TTL来老化数据,可能会增加compact负担
需要保证查询操作不会查询到过期数据,否则会影响系统性能。
根据以上对比,如果在系统压力不是特别大,需要长期运行,能够控制查询不会查询到过期数据的场景下,建议使用TTL+循环key的方式,否则建议使用按照时间周期进行建表的方式。

索引

hbase的主要索引方式还是通过rowkey,官方并不提供二级索引。
华为hbase团队是采用表数据和index数据分表查询,这里region分区很复杂,且多表数据一致性处理也很复杂。
360分享了一个二级索引的方案,很巧妙,可以参考
http://www.infoq.com/cn/presentations/qihoo360-hbase-two-stage-index-design-and-practice

单列索引

思路很简单,做列->rowkey的映射就可以了,因为hbase自带的rowkey->列的索引,所以就可以查出所有数据。
索引和数据在同一个region内,但索引数据存储在单独index family中,360固定一个family就叫INDEX。在一个region,所以写不需要额外的操作,只需要把索引信息也加到put中。
为了保证在同一个region中,所以索引创建的反序列化信息要以region startkey开头。

构建索引查询,如c21,就构造region的startkey+index+c21‘,c21’是刚好大于c21的一个值。

多列索引

在单列索引的情况下,所列结合在一起,看图明意:

region分裂
原表schema变化

通过重建索引的方式解决

索引下的查询优化

• 列和时间建立联合索引
• 常用组合查询建立联合索引
• 查询表达式的转换(A | B) & (C | D) à (A&C) | (A&D) | (B&C) | (B&D)

模糊查询

引入搜索,如solr。

事务

Hbase并不支持跨行和跨表的ACID。设计上要考虑这种事务缺失带来的影响。
如果确实要用事务,需要第三方协调机制,如借助zk的分布式协调,做事务控制。

JVM配置

这里主要指region server的配置,因为hbase的主要负载不在master。
按业务需要指定堆大小,特别注意新生代的大小,遇到过minor GC过多的问题。且hbase快速flush的数据大都在年轻代搞定,所以要适当调大年轻代大小 ,一般不使用默认值。
垃圾回收指定CMS+parnew,建议开启gc日志
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:xxx/xxx.log"
region server设置60%的堆占用率,其中20%块缓存+40%memstore,所以CMS收集率要调到70%,-XX:CMSInitiatingOccupancyFraction=70,也需要观察实际情况做调整。

Memstore-Local Allocation Buffer

MSLAB的引入是为了解决内存空洞的问题,因为长期存在的keyvalue会占用老年代,一旦flush到磁盘,就会产生碎片,碎片多了之后,就会产生stop the world GC。
MSLAB设计会减少碎片,核心思路是从堆分配相同大小的对象,回收后会产生固定大小的空洞,之后分配也是同样大小的对象,这样就不会有大量碎片空间而无法分配内存的问题。
当一个分配区不足以放下一个KeyValue时,就会认为这个分配区已满,所以这里面也有一定的浪费。
是否使用MSLAB通过hbase.hregion.memstore.mslab.enabled参数配置。
分配区大小默认是2MB,通过hbase.hregion.memstore.mslab.max.allocation参数调整。
KeyValue大小有控制,默认大于256KB的就会绕过MSLAB,直接申请堆内存,所以大的keyvalue多了之后,MSLAB作用就小的多了。这个上限通过hbase.hregion.memstore.mslab.max.allocation参数修改。

压缩

Hbase支持多种压缩算法,各有特点,如LZO速率快,但是压缩率低。GZIP压缩率高,但速率慢,snappy相对来说更均衡。使用哪种方式也结合业务情况。
如果保存已经压缩过的数据,就不需要启动压缩了,如JPG图片。
压缩可以配置在family上。

region拆分与合并

识别可能的热点,设置splitkey,进行region预分。
这个方案适合非完全连续的行健,如果完全连续,一段时间内的插入数据总会集中到最近生成的几个region上。
可以先控制拆分10个region,或根据实际情况调整,因为region数量会影响集群性能,所以不能过多,也不能有空region。
可以按照region中最大的存储文件大小决定预拆分region的数量,最大的region刚好跳过major合并,就会大大减少compact风暴。
region的调整,hbase提供了负载均衡机制,默认每五分钟尝试分配region平衡到所有region server上。运行时间SLA设置为间隔时间的一半。
默认region大小为256M,为了保证适当大小的region长期稳定,可以调整到1G。注意业务按需,和调大后GC停顿变长的问题。

客户端API的一些优化汇总

禁止自动flush

有大量写入时,setAutoFlush(false),否则put会逐个提交,通过批量缓存,一次提交提高吞吐量。

使用Scanner Caching

还是提高缓存提升批量处理效率的思路,但是注意要衡量缓存大了之后内存占用高的问题。

缩小scan范围

注意scan添加列,剔除无效列的查询

资源关闭

如HTable,getScanner()得到的ResultScanner,要在finally关闭。

块缓存

频繁使用的行,要开启这个功能。
大批量扫描,如MR的集成,要关闭这个功能。

只取rowkey的优化

添加MUST_PASS_ALL策略的filter,指定FirstKeyOnlyFilter 和KeyOnlyFilter,可以减少大量的网络传输,只返回第一个发现的keyvalue的行健。

慎重关闭WAL

大量的Put操作,且急切要提升写入效率,可以考虑关闭WAL。但是有丢数据的风险。

Zookeeper超时现象

zk的超时有zookeeper.session.timeout参数控制,默认是3分钟,根据业务需要可以调高和调低这个值,但是要注意的是,要考虑GC停顿的时间,否则就会造成假死现象,进程还在,但是master已经认为region server挂了。

region server线程池

默认region server响应外部请求的线程数为10,由参数hbase.regionserver.handler.count控制,这个值主要考虑单次请求吞吐量大的场景,一般是MB级别,如果单次请求较小,如get,小put等,可以考虑调高这个值。
但是调高这个值会增加server端的OOM异常风险,使GC更频繁。
可以配合调高region server的堆大小,甚至有方案调到了8G以上,更夸张的也有。

块缓存

堆中块缓存大小默认为20%(0.2),由参数perf.hfile.block.cache.size控制,监控是否有多块被换入换出的情况,如果有,就要考虑增大这个配置。
块缓存增加的场景大多在主读的场景。
另外,和下面说的memstore配置,加起来是不能超过100%的,默认是60%

memstore大小

这个配置默认是上限40%(0.4),下限35%,由参数hbase.regionserver.global.memstore.upperLimit和hbase.regionserver.global.memstore.lowerLimit控制上限和下限,
上下限这么接近,是为了避免过度的flush。
如果是读多的场景,可以考虑同时降低上下限,给块缓存留出更多位置。

更新阻塞

参考http://gbif.blogspot.com/2012/07/optimizing-writes-in-hbase.html
有两种阻塞更新的阈值,一个是hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier,默认是64M*2,达到这个阈值会阻塞客户端请求,来等待完成flush。
还有一个是hbase.hstore.blockingStoreFiles ,默认是7,memstore flush会创建新的storefile,然后通过大小合并去缩减数量,默认大于3个storefile就启动compact,一次最多compact7个文件,所以这个值默认是7.
当存储空间足够时,可以考虑增大这几个配置。以应对突发大容量写入的场景。

性能压测的工具

hbase自带一个评估工具,默认是用MR任务评估,可以指定--nomapred:
./bin/hbase org.apache.hadoop.hbase.PerformanceEvaluation

Yahoo提供了一个YCSB,据说比自带的评估工具强大,未用过。

故障检测修复工具

hbase自带了一个hbck工具,可以通过帮助看用法
./bin/hbase hbck –h
hbck会扫描.META.表,收集信息。还会扫描hbase使用的HDFS中的root目录。然后会比较收集信息来生成一致性和完整性报告。
一致性检查以region为单位,检查region是否同时存在于.META.表和HDFS中,并检查是否被唯一指派给某个region server
完整性检查以表为单位,将region与表细节信息进行对比,找到缺失的region,同时检查region起止键范围中的空洞或重叠情况

fix选线可以修复检查出来的问题,如.META.没有被分配,则会分配到一个新服务器,如果.META.被重复分配,会修复。如果用户表的region没有分配、或分配到多个region server,会重新分配。如果region当前所在server和.META.中描述不同,则会重新分配region。

作者 | 林钰鑫

转载请注明出处:华为云博客 https://portal.hwclouds.com/blogs

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。