场景:
生产查询系统用hbase+es构建,一套老的hdp集群,一套新的cdh集群。cdh集群数据由hdp迁移过来,目前已全部完成,完成后两套集群双写,写入方式都是通过spark put方式(也可以用bulkload)。但是会出现一种现象,就是cdh集群写的速度比hdp要慢1/2左右,数据量小不太明显,数据量大的时候特别明显。
问题描述:
当写入数据量特别大或者字段特多的时候,hdp能正常写入,cdh会很快失败,并报一个错:Hbase RegionTooBusyException:over memstore limit =512.0m。
也就是在hbase集群中向几个大表put数据时,会出现RegionTooBusyException的异常,该异常报错为超过了memstore的上限。
问题分析:
查阅资料,发现这个over memstore limit =512.0m 是这么来的:hbase.hregion.memstore.flush.size(默认128M)*hbase.hregion.memstore.block.multiplier(默认4)=512M。
解决方式:
既然这样,那调整这两个参数是不是就可以了,于是,根据不同hbase表写入量大小,调整hbase.hregion.memstore.flush.size值为256M,512M,最大的调了2G。
查询超时:
开始大部分hbase表都能写成功了,但是到后面慢慢的又不行了,甚至在写大表的时候会造成很多查询超时,这种情况是无法接受的。当时已经造成了一定生产影响,只能把cdh大数据写入的停掉。
思考:
实际上还是没有理解hbase写入原理,随意的修改生产参数,险些造成验证的生产事故。
重新分析:
cdh的hbase版本比hdp的要高,正常不可能出现hdp成功,cdh失败的情况才对。肯定是hdp调整了某些参数,cdh没有调。对比发现果然有一个重要的参数cdh没有调,就是hbase.hstore.blockingStoreFiles。hdp默认是10,运维调成了500,而cdh这个参数是16。这个参数有什么作用,也写参考了下面的文章才知道。
原理参考:
文末参考链接,内容借鉴过来记录一下:
异常代码片段:
hdp版hbase0.98:
private void checkResources()
throws RegionTooBusyException {
// If catalog region, do not impose resource constraints or block updates.
if (this.getRegionInfo().isMetaRegion()) return;
if (this.memstoreSize.get() > this.blockingMemStoreSize) {
blockedRequestsCount.increment();
requestFlush();
throw new RegionTooBusyException("Above memstore limit, " +
"regionName=" + (this.getRegionInfo() == null ? "unknown" :
this.getRegionInfo().getRegionNameAsString()) +
", server=" + (this.getRegionServerServices() == null ? "unknown" :
this.getRegionServerServices().getServerName()) +
", memstoreSize=" + memstoreSize.get() +
", blockingMemStoreSize=" + blockingMemStoreSize);
}
}
cdh版hbase2.1:
void checkResources() throws RegionTooBusyException {
// If catalog region, do not impose resource constraints or block updates.
if (this.getRegionInfo().isMetaRegion()) return;
MemStoreSize mss = this.memStoreSizing.getMemStoreSize();
if (mss.getHeapSize() + mss.getOffHeapSize() > this.blockingMemStoreSize) {
blockedRequestsCount.increment();
requestFlush();
// Don't print current limit because it will vary too much. The message is used as a key
// over in RetriesExhaustedWithDetailsException processing.
throw new RegionTooBusyException("Over memstore limit=" +
org.apache.hadoop.hbase.procedure2.util.StringUtils.humanSize(this.blockingMemStoreSize) +
", regionName=" +
(this.getRegionInfo() == null? "unknown": this.getRegionInfo().getEncodedName()) +
", server=" + (this.getRegionServerServices() == null? "unknown":
this.getRegionServerServices().getServerName()));
}
}
Region在每次进行put时,会进行resource的检查。
默认情况下:512M = memstore(128M) * multiplier(4)
原因分析:
一般来说memstore超过hbase.hregion.memstore.flush.size(默认128M),会flush形成HFile。
当写入数据过快时,导致产生大量HFile,当HFile数量超过配置hbase.hstore.blockingStoreFiles(默认10),hbase会进行compaction(合并),compaction会阻塞memstore flush操作,阻塞最长时长hbase.hstore.blockingWaitTime(默认值90000,即90s),当超过该时间后,如果compaction还未完成,memstore flush也会停止阻塞。
但是正是在flush阻塞这段时间内,memstore的大小超过了上限:
hbase.hregion.memstore.flush.size*hbase.hregion.memstore.block.multiplier(默认为4)=512M,此时,region将拒绝所有写请求,所以客户端抛出RegionTooBusyException,并在一定时间后重试。
解决方案:
1.在hbase内存允许的前提下,提高hbase.hregion.memstore.block.multiplier或者hbase.hregion.memstore.flush.size参数,在flush阻塞的这段时间,允许更多的数据写到memstore。
风险:增加了regionserver oom概率,修改该参数,需要进行大数据量写入测试。
我就是改了这个参数,险些造成严重的生产事故。
2.减少阻塞时间hbase.hstore.blockingwaittime(比如到30s),加快memstore flush到hdfs上的速度,从而减少memstore数据大小触碰到上限的时间,也就能减少拒绝写请求的时间。
风险:1、增加了compaction的压力,
2、占用磁盘IO,可能会影响到其他服务。
3.一般出现RegionTooBusyException,表示大量写入数据量比较大,这种场景更好的选择可能是采用bulkload导入方式。
事实证明,大数据量的改bulkload方式也不行,还是会报错,我试过。
4.调大hbase.hstore.blockingStoreFiles(默认10) ,比如改成500,调大这个参数,减缓compaction(合并),进而减小阻塞队列。网上还有说是GC原因的,怎么调都不好使。
这个生产hdp环境已经试验过可行,而且肯定是之前运维已经趟过水了,可惜调优的材料也没见着。。
优化点
- hbase.hregion.flush.size 默认128M 调大 有风险
- hbase.regionserver.global.memstore.lowerLimit 默认0.95 regionserver级别 慎改
- hbase.hregion.memstore.block.multiplier 默认4
- hbase.regionserver.global.memstore.upperLimit 默认0.4
- hbase.hstore.blockingStoreFiles 默认10,调成500或者更大
- hbase.hregion.max.filesize 默认35G 调成1000G
- 全量表删除重建,regions调大,最好节点数整数倍
- 手动合并hfile
- hbase.hstore.blockingWaitTime 默认90秒
- 全量大表调整跑批频率,每天跑改成每周两三次,除非业务硬性要求每天跑,hbase会自动合并小文件,跑的太频繁来不及合并
附:生产调优配置
模块 | 参数名 | old | new |
server | hbase master maximum memory | 2GB | 4GB |
server | number of handlers per gegionserver | 30 | 300 |
server | hbase regionserver maximum memory | 20GB | 50GB |
client | maximum record size | 1MB | 10MB |
disk | maximum region file size | 10GB | 1000GB |
disk | hbase.hregion.majorcompaction | 7days0hours | 0days0hors |
disk | maximum files for compaction | 10 | 12 |
timeouts | zookeeper session timeout | 90s | 120s |
timeouts | hbase rpc timeout | 90s | 240s |
regionserver | regionservers maximum value for -Xmn | 512MB | 4096MB |
general | maximum store files before minor compaction | 3 | 6 |
advanced hbase-env | hbase_user_nofile_limit | 32000 | 65535 |
advanced hbase-site | hbase.coprocessor.region.classed | org.apache.hadoop .security.access. securebulkloadendpoing | |
advanced hbase-site | hstore blocking storefiles | 10 | 500 |
advanced hbase-site | hbase.bucketcache.ioengine | offheap | |
advanced hbase-site | hbase.bucketcache.percentage. in.combinedcache | 1 | |
advanced hbase-site | hbase.bucketcache.size | 10240 | |
custom hbase-site | hbase.client.scanner.timeout.period | undefined | 240000 |
custom hbase-site | hbase.hstore.flusher.count | undefined | 20 |
custom hbase-site | hbase.regionserver.optionalcacheflushinterva | undefined | 36000000 |
custom hbase-site | hbase.regionserver.regionSplitLimit | undefined | 1 |
custom hbase-site | hbase.regionserver.thread.compaction.large | undefined | 8 |
custom hbase-site | hbase.regionserver.thread.compaction.small | undefined | 5 |
custom hbase-site | hbase.snapshot.master.timeout.millis | undefined | 1800000 |
custom hbase-site | hbase.snapshot.timeout.millis | undefined | 1800000 |
custom hbase-site | hbase.zookeeper.property.tickTime | undefined | 6000 |
参考:
HBase写入报错:org.apache.hadoop.hbase.RegionTooBusyException: Above memstore limit_没有格子衬衫的程序员的博客-优快云博客