教程集 www.jiaochengji.com
教程集 >  数据库  >  mysql  >  正文 HBase数据库性能优化总结笔记

HBase数据库性能优化总结笔记

发布时间:2017-12-12   编辑:jiaochengji.com
教程集为您提供HBase数据库性能优化总结笔记等资源,欢迎您收藏本站,我们将为您提供最新的HBase数据库性能优化总结笔记资源
HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。本教程我们讨论一下HBase数据库性能优化。

垃圾回收优化

master基本不会遇到垃圾回收的问题。
由于memstore的刷写机制是不连续的,所以java虚拟机的堆内存会出现孔洞。
快速刷写到磁盘的数据会被划分到新生代,这种空间会被优先回收
数据停留的时间太长,会被划分到老生代甚至终生代。而且老生代和终生代一般占据了好几个G,而新生代一般就几百M而已

新生代空间
由此得出新生代的空间一般的分配如下

    -XX:MaxNewSize=128m -XX:NewSize=128m  

可以缩写为


    -Xmn128m  

设定好之后观察是否合理如果不合理你会发现服务器的CPU使用量急剧上升,因为新生代的回收很占CPU
新生代的设定如果调大,会带来的好处:则生存期较长的对象不会过快的划分为老生代。
如果太大,回收会产生较长时间停顿

gc日志
如果JRE中孔洞太多,空间不够的时候,就需要压缩堆内存碎片,如果压缩内存碎片失败会出现失败日志。所以要通过以下参数开启jvm的gc日志


    -verbose:gc -XX: PrintGCDetails -XX: PrintGCTimeStamps -Xloggc:$HBASE_HOME/logs/gc-${hostname}-hbase.log  

日志中会出现 "concurrent mode failure" 或者 "promotion failed" 信息注意:不过这个日志不会自动滚动,会越来越大你的手动用linux的每日滚动去做手动清理

回收策略
垃圾回收策略是可以切换的,建议用以下策略


    -XX: UseParNewGC and -XX: UseConcMarkSweepGC  

第一个选项是设置年轻代用 Parallel New Collector 回收策略:停止jvm去清空年轻代。因为年轻代很小,所以这个过程很快,一般不到一秒,所以这个暂停是可以接受的

CMS策略
但是老生代不能用这个策略,因为老生代很大,暂停会很久,如果大于zk的会话超时,就会引起朱丽叶暂停问题。所以老生代用 并行标记回收器(Concurrent Mark-Sweep Collector, CMS)来缓解。这种策略尽量异步实现垃圾回收,但是cpu占用率高。不过如果回收失败的话,还是会让jvm暂停来进行内存整理。使用了CMS策略有一个额外的参数设定什么时候开始进行并发标记


    -XX:CMSInitiatingOccupancyFraction=70  

这个值设定了一个百分比。70%是一个比较好的值,因为它比region的堆占用率60%略大(20%块缓存 40%memstore)
这样在堆空间被占完之前就开始并行回收
不会太小而导致回收频繁进行

优化原则
块缓存 memstore 不能 大于 100%
要留空间给其他操作,所以 块缓存 memstore = 60% 比较合理

本地memstore分配缓冲区(MSLAB)
MSLAB=Memstore-Local Allocation Buffers 本地memstore分配缓冲区jvm孔洞(碎片)如果太多会触发 stop-the-world 垃圾回收,就是把整个jvm停掉回收垃圾。所以MSLAB致力于减少碎片。方法是:每次分配固定大小的对象,当这些对象被回收的时候,会留下固定大小的孔洞,之后如果新对象的大小也相同就可以直接用这些孔洞了,就不会引发 promotion fail,就不会触发 stop-the-world 过程MSLAB默认是开启的,如果没有就设置 hbase.hregion.memstore.mslab.enabled来开启
hbase.hregion.memstore.mslab.chunksize 可以设定之前所说的固定大小孔洞的大小,默认是2MB。如果你存储的东西都很大,那就调大这个值
如果你要存的东西大于存储缓冲区的上边界 hbase.hregion.memstore.mslab.max.allocation 默认值是256K。任何大于该值的单元格不会使用mslab特性,而是直接向jvm申请空间。
MSLAB的代价是空间的浪费,就算你没用到缓冲区的最后一个字节,缓冲区依然是那么大。所以你必须权衡利弊(我个人建议是浪费就浪费,总比引起jvm暂停好)
使用缓冲区需要额外的内存复制工作,所以会比直接使用KeyValue实例要慢一点

压缩
推荐使用snappy 。不过要在一开始就使用,中间切换不好搞

优化拆分和合并

管理拆分
拆分/合并风暴
当用户的region大小以恒定的速度保持增长时,region拆分会在同一时间发生,因为同时需要压缩region中的存储文件,这个过程会重写拆分后的region,这将会引起IO上升。建议:关闭自动拆分,然后手动调用split和major_compact 命令
如何关闭自动拆分?
将 hbase.hregion.max.filesize 调的非常大,但是不要大过 Long.MAX_VALUE (即 9223372036854775807),建议为100G手动运行还有一个好处,可以在不同时间段不同的region上执行,分散压力。
用户可以做成cron的job定时执行这些操作
手动拆分可以避免:当你做troubleshooting 的时候自动拆分有可能会把你正在看的region拆掉,这样就不好了

region热点
注意不要用类似时间戳这样的递增的东西做主键,防止出现region热点

预拆分region
在建立表的时候通过 SPLITS 属性可以直接定义各个region的范围,进行region的预拆分

负载均衡
master有一个内置的均衡器。默认情况下,均衡器每五分钟运行一次,这是通过 hbase.balancer.period 属性设置的。它会尝试均匀分配region到所有region服务器。启动均衡器,均衡器首先会确定一个region分配计划,该计划用于描述region如何移动。然后通过迭代调用管理API中的 unassign() 方法开始移动region。均衡器有一个可以限制自身运行时间的上限,通过 hbase.balancer.max.balancing 属性来配置,默认设置为均衡器运行时间间隔周期的一半,即两分半钟。

合并region
用 hbase org.apache.hadoop.hbase.util.Merge testtable 可以合并多个region
删除大量数据的时候,可以合并region,让region不会那么多
客户端API:最佳实践

禁止自动刷写
如果有大量的写入操作时,使用setAutoFlush(false) ,否则 Put 实例会被逐个传送到region服务器。禁止了自动刷写就可以等到写缓冲区被填满的时候一次性批量的发送。
你可以可以使用 flushCommits() 方法显式刷写数据
用 HTable 的 close 方法也会隐式的调用刷写

使用扫描缓存
如果HBase被作为一个MapReduce 作业的输入源,就可以用 setCache() 设置一个比1大的多的数值,可以开启扫描缓存。这样可以一次从region取多条(比如500条)到客户端来处理。
不过传输数据的开销和内存开销都会增大。所以不是越大越好

限定扫描范围
当Scan被用来处理大量行时(比如作为MapReduce输入源时)最好只设定指定的列,如果用addFamily() 会把整个family的所有列都加载进来。(其实就是跟传统SQL建议大家不要 SELECT * 一回事)

关闭ResultScanner
一定要记得及时关闭 ResultScanner (其实跟传统数据库要记得关闭连接一回事)
在finally 里面关闭 ResultScanner

块缓存用法
Scan可以通过设置 setCacheBlocks() 来设置使用region服务器中的块缓存。
如果在MapReduce中,这个应该被设置成false
如果某些行被频繁访问,这个应该被设置成true

优化获取行键的方式
如果你只是进行某些简单的行统计之类不需要获取所有列的操作,记得在 FilterList中添加 FirstKeyFilter 或者 KeyOnlyFilter ,这样就可以只返回第一个KeyValue行键,极大的减少了网络传输

关闭Put上的WAL
Put 的 writeToWAL(false) 可以关闭WAL,可以大幅提高吞吐量,但是副作用就是region如果出问题就会丢失数据。
其实如果数据是在集群间分布均匀后,其实关闭日志不会提升多少性能
所以最好不要关闭WAL。要是真的要提高吞吐量的话就用 批量导入 (bulk load) 技术。这个在 12.2.3 中会进行介绍

配置
减少ZooKeeper超时的发生
默认的region和zk之间的超时时间是3分钟。推荐设置为1分钟,这样可以更快的发现这一个故障
默认时间那么长是为了避免大数据到导入时出问题,如果没有大数据的导入情况就可以把超时设置短一点
12.5.3中“稳定性问题”会介绍一个方法来检测这种停顿
个人认为没有太大必要,要挂就是一直挂,快那么2分钟也没什么用

增加处理线程
hbase.regionserver.handler.count 定义了响应外部用户访问数据表请求的线程数,默认是10,有点偏小。这是为了防止用户在客户端高并发使用较大的缓冲区的情况下服务器端过载。
但是当单次请求开销较小时,可以设定的高一点
设置太高,会对region的内存造成压力,甚至会导致 OutOfMemoryError。而且可用内存过低的话又会触发垃圾回收,造成全面暂停

增加堆大小
在 hbase-env.sh 中 调整 HBASE_HEAPSIZE ,增大为8G
不过最好用 HBASE_REGIONSERVER_OPTS 而不是 HBASE_HEAPSIZE 可以单独调大region的堆大小,master不需要太大的堆大小,1G就够用了

启用数据压缩
推荐snappy,如果没有snappy就用LZO压缩

增加region大小
更大的region可以减少region数量
少region可以让集群运行更平稳
如果一个region变热点就手动拆分它
默认的region是256M,可以配置成1G或者更大
region太大的话高负载下的合并会停顿很长时间
通过 hbase.hregion.max.filesize 设置region的大小

调整块缓存大小
默认块缓存是20%(即0.2)
通过 perf.hfile.block.cache.size 属性可以设置这个百分比
如果根据10.2.3节提到的 evicted(LV) 参数发现有许多块被换出。这样就需要增加块缓存大小来容纳更多的块
如果用户负载基本都是读请求,也可以增加块缓存
块缓存 memstore上限不能超过100%。默认他们的和是60%,只有当用户确认有必要并且不会造成副作用时才调整

调整memstore限制
通过 hbase.regionserver.global.memstore.upperLimit 设置上限。默认是0.4
hbase.regionserver.global.memstore.lowerLimit 设置下限。默认是0.35
把上下限设置的接近一点来避免过度刷写
如果主要处理读请求,可以考虑同时减少memstore的上下限来增加块缓存的空间。
如果刷写的数据量很小,比如只有5MB,就可以增加存储限制来降低IO操作
增加阻塞是存储文件数目
hbase.hstore.blockingStoreFiles 设置,决定了当存储文件的数据达到阀值时,所有更新操作(put,delete)等会被阻塞。然后执行合并操作。默认值是7
如果文件数一直很高,就不要提高该配置项,因为这样只会延迟问题的发生,而不能避免

增加阻塞倍率
hbase.hregion.memstore.block.multiplier 的默认值为2 。当memstore达到属性 multiplier 乘以 flush 的大小限制时会阻止进一步的更新
当有足够的存储空间时,用户可以增加这个值来增加平滑的处理写入突发流量

减少最大日志
设置 hbase.regionserver.maxlogs 属性是的用户基于磁盘的WAL文件数目,控制刷写频率。默认值是32
对于写压力比较大的应用来说要把这个值调低,可以让数据更频繁的写到硬盘上,这样已经刷写到硬盘上的日志就可以被丢弃

负载测试

PE
HBase有自带的压力测试工具名叫 PE(Performance Evaluation)
YCSB
Yahoo 推出的云服务基准测试工具。比PE更好用,可以对hbase进行压力测试
YCSB提供了更多的选项,并且能够将读写负载混合在一起


HBase 性能优化笔记

1 hbase.hregion.max.filesize应该设置多少合适

默认值:256M
说明:Maximum HStoreFile size. If any one of a column families' HStoreFiles has grown to exceed this value, the hosting HRegion is split in two.

HStoreFile的最大值。如果任何一个Column Family(或者说HStore)的HStoreFiles的大小超过这个值,那么,其所属的HRegion就会Split成两个。

调优:

hbase中hfile的默认最大值(hbase.hregion.max.filesize)是256MB,而google的bigtable论文中对tablet的最大值也推荐为100-200MB,这个大小有什么秘密呢?
  众所周知hbase中数据一开始会写入memstore,当memstore满64MB以后,会flush到disk上而成为storefile。当storefile数量超过3时,会启动compaction过程将它们合并为一个storefile。这个过程中会删除一些timestamp过期的数据,比如update的数据。而当合并后的storefile大小大于hfile默认最大值时,会触发split动作,将它切分成两个region。
  lz进行了持续insert压力测试,并设置了不同的hbase.hregion.max.filesize,根据结果得到如下结论:值越小,平均吞吐量越大,但吞吐量越不稳定;值越大,平均吞吐量越小,吞吐量不稳定的时间相对更小。

  为什么会这样呢?推论如下:

    a 当hbase.hregion.max.filesize比较小时,触发split的机率更大,而split的时候会将region offline,因此在split结束的时间前,访问该region的请求将被block住,客户端自我block的时间默认为1s。当大量的region同时发生split时,系统的整体访问服务将大受影响。因此容易出现吞吐量及响应时间的不稳定现象
    b 当hbase.hregion.max.filesize比较大时,单个region中触发split的机率较小,大量region同时触发split的机率也较小,因此吞吐量较之小hfile尺寸更加稳定些。但是由于长期得不到split,因此同一个region内发生多次compaction的机会增加了。compaction的原理是将原有数据读一遍并重写一遍到hdfs上,然后再删除原有数据。无疑这种行为会降低以io为瓶颈的系统的速度,因此平均吞吐量会受到一些影响而下降。
    综合以上两种情况,hbase.hregion.max.filesize不宜过大或过小,256MB或许是一个更理想的经验参数。对于离线型的应用,调整为128MB会更加合适一些,而在线应用除非对split机制进行改造,否则不应该低于256MB

2 autoflush=false的影响

  无论是官方还是很多blog都提倡为了提高hbase的写入速度而在应用代码中设置autoflush=false,然后lz认为在在线应用中应该谨慎进行该设置。原因如下:

  a autoflush=false的原理是当客户端提交delete或put请求时,将该请求在客户端缓存,直到数据超过2M(hbase.client.write.buffer决定)或用户执行了hbase.flushcommits()时才向regionserver提交请求。因此即使htable.put()执行返回成功,也并非说明请求真的成功了。假如还没有达到该缓存而client崩溃,该部分数据将由于未发送到regionserver而丢失。这对于零容忍的在线服务是不可接受的。

  b autoflush=true虽然会让写入速度下降2-3倍,但是对于很多在线应用来说这都是必须打开的,也正是hbase为什么让它默认值为true的原因。当该值为true时,每次请求都会发往regionserver,而regionserver接收到请求后第一件事就是写hlog,因此对io的要求是非常高的,为了提高hbase的写入速度,应该尽可能高地提高io吞吐量,比如增加磁盘、使用raid卡、减少replication因子数等

3 从性能的角度谈table中family和qualifier的设置
  对于传统关系型数据库中的一张table,在业务转换到hbase上建模时,从性能的角度应该如何设置family和qualifier呢?
  最极端的,①每一列都设置成一个family,②一个表仅有一个family,所有列都是其中的一个qualifier,那么有什么区别呢?

  从读的方面考虑:
  family越多,那么获取每一个cell数据的优势越明显,因为io和网络都减少了。

  如果只有一个family,那么每一次读都会读取当前rowkey的所有数据,网络和io上会有一些损失。

  当然如果要获取的是固定的几列数据,那么把这几列写到一个family中比分别设置family要更好,因为只需一次请求就能拿回所有数据。

  从写的角度考虑:

  首先,内存方面来说,对于一个Region,会为每一个表的每一个Family分配一个Store,而每一个Store,都会分配一个MemStore,所以更多的family会消耗更多的内存。
  其次,从flush和compaction方面说,目前版本的hbase,在flush和compaction都是以region为单位的,也就是说当一个family达到flush条件时,该region的所有family所属的memstore都会flush一次,即使memstore中只有很少的数据也会触发flush而生成小文件。这样就增加了compaction发生的机率,而compaction也是以region为单位的,这样就很容易发生compaction风暴从而降低系统的整体吞吐量。
  第三,从split方面考虑,由于hfile是以family为单位的,因此对于多个family来说,数据被分散到了更多的hfile中,减小了split发生的机率。这是把双刃剑。更少的split会导致该region的体积比较大,由于balance是以region的数目而不是大小为单位来进行的,因此可能会导致balance失效。而从好的方面来说,更少的split会让系统提供更加稳定的在线服务。而坏处我们可以通过在请求的低谷时间进行人工的split和balance来避免掉。
     因此对于写比较多的系统,如果是离线应该,我们尽量只用一个family好了,但如果是在线应用,那还是应该根据应用的情况合理地分配family。

4 hbase.regionserver.handler.count

 RegionServer端开启的RPC监听器实例个数,也即RegionServer能够处理的IO请求线程数。默认是10.

 此参数与内存息息相关。该值设置的时候,以监控内存为主要参考。

 对于 单次请求内存消耗较高的Big PUT场景(大容量单次PUT或设置了较大cache的scan,均属于Big PUT)或ReigonServer的内存比较紧张的场景,可以设置的相对较小。

 对于 单次请求内存消耗低,TPS(TransactionPerSecond,每秒事务处理量)要求非常高的场景,可以设置的相对大些。

您可能感兴趣的文章:
HBase数据库性能优化总结笔记
影响MySQL性能的查询类型有哪些
笔记本电脑散热不好什么原因,笔记本散热差的解决办法
mysql数据库性能优化技巧
高性能mysql(第二版)之查询性能优化
python数据分析师需要学什么
想系统学习GO语言(Golang
超全的!Redis的安装和基础操作
神舟优雅A480N-i5D3笔记本维修笔记
Golang笔记:语法,并发思想,web开发,Go微服务相关

[关闭]
~ ~