现象:
周末收到一个表持续数据核对不一致问题,核对最早的记录发现在HBase集群中是存在的,新的记录在HBase中不存在,基本确认是消费延迟的问题。
定位问题:
目前的消费延迟有两类:
1、采集延迟,这种情况可能是DB延迟或者是采集慢的问题,检查告警没发现有采集延迟的告警,因此这种情况可以忽略;
2、消费延迟,这种一般是由于某个regionserver性能有问题或者是业务写入量太大或者是region有数据热点引起。
登录查看消费曲线发现对应的group消费延迟比较大,并且还在持续增长中。如下图:
初步定位是消费延迟引起的数据不一致。接下来就要确定该是什么原因导致。
查看tdsort的日志发现是如下错误日志:
文本内容为:
2018-10-20 23:08:42 AsyncProcess [INFO] #44085, table=t_medi_bankroll_list_Fuid_201810, attempt=2/35 failed=21ops, last exception: org.apache.hadoop.hbase.RegionTooBusyException: org.apache.hadoop.hbase.RegionTooBusyException: Above memstore limit, regionName=t_medi_bankroll_list_Fuid_201810,506,1538133733028.010ea7f2d793d3866fa035c65fa61f8a., server=xx.xx.xx.xx,60020,1533110695227, memstoreSize=805656832, blockingMemStoreSize=805306368
问题比较明显了,意思是memstoreSize达到了805656832,超过了HBase block的阀值,导致对应的region被阻塞。
再进到对应region所在的regionserver上,查看日志,内容如下:
2018-10-20 23:04:19,856 WARN [B.defaultRpcServer.handler=1,queue=1,port=60020] regionserver.HRegion: Above memstore limit, regionName=t_medi_bankroll_list_Fuid_201810,506,1538133733028.010ea7f2d793d3866fa035c65fa61f8a., server=xx.xx.xx.xx,60020,1533110695139, memstoreSize=806026912, blockingMemStoreSize=805306368
这里确认是memstore写得太大引起。
知识补充:
HBase会检查Memstore的大小,如果Memstore超过设定的blockingMemStoreSize则触发flush的操作,并抛出RegionTooBusyException,阻塞写操作的进行。如下源码所示:
private void checkResources()
throws RegionTooBusyException {
// If catalog region, do not impose resource constraints or block updates.
// 如果是Meta Region,不实施资源约束或阻塞更新
if (this.getRegionInfo().isMetaRegion()) return;
// 如果Region当前内存大小超过阈值
// 这个memstoreSize是当前时刻HRegion上MemStore的大小,它是在Put、Append等操作中调用addAndGetGlobalMemstoreSize()方法实时更新的。
// 而blockingMemStoreSize是HRegion上设定的MemStore的一个阈值,当MemStore的大小超过这个阈值时,将会阻塞数据更新操作
if (this.memstoreSize.get() > this.blockingMemStoreSize) {
// 更新阻塞请求计数器
blockedRequestsCount.increment();
// 请求刷新Region
requestFlush();
// 抛出RegionTooBusyException异常
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);
}
}
blockingMemStoreSize的大小由hbase.hregion.memstore.flush.size和hbase.hregion.memstore.block.multiplier共同作用,等于两者相乘,我们的hbase.hregion.memstore.flush.size设置的是256M,hbase.hregion.memstore.block.multiplier设置的是3,因此:blockingMemStoreSize=805306368。具体的blockingMemStoreSize计算的代码如下:
this.blockingMemStoreSize = this.memstoreFlushSize *
conf.getLong(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER,
HConstants.DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER);
原因:
这里问题就来了,为什么t_medi_bankroll_list_Fuid_201810的010ea7f2d793d3866fa035c65fa61f8a的Memstore会如此巨大?答案就是前面我们说到的造成消费延迟的原因之一:数据热点。下面从region大小来分析数据热点的问题:
从上图中可以看到,其他的region基本都在3G左右,而这个region却有276G,热点台明显,基本写入都集中在了这个region了,难怪会造成消费延迟。根本原因还是表结构设计的不合理,后面会专门针对表结构设计的问题写文章,敬请期待。
备注:线上的表关闭了自动分裂
解决办法:
临时解决办法是直接手工分裂该region,并将分裂后的两个region迁移到比较空闲的机器上,操作命令如下:
split ‘010ea7f2d793d3866fa035c65fa61f8a'
move 'cdf6287ea98622034c5fa4441a6dda86’,’x.x.x.x1,60020,1539239813873'
move '8df338fe3b7643a201a74d856ff2fa4d',’x.x.x.x2,60020,1539239808775'
备注:010ea7f2d793d3866fa035c65fa61f8a这个region名称是从日志中定位确认有问题的region,如下日志:
regionName=t_medi_bankroll_list_Fuid_201810,506,1538133733028.010ea7f2d793d3866fa035c65fa61f8a., server=xx.xx.xx.xx,60020,1533110695227, memstoreSize=805656832,
另外两个region是010ea7f2d793d3866fa035c65fa61f8a分裂后的region名称,可以通过hbase的管理页面中查看表的t_medi_bankroll_list_Fuid_201810的506对应的分裂后的region。如下图:
为了提高后面的查询性能,最好再对这两个region做大合并操作。
如果要根治,还是需要从表结构入手去优化。
分裂后,消费延迟很快就恢复了:
总结:
这是一起很典型的由于数据热点问题导致的HBase入库延迟的案例,这种问题临时解决办法是分裂,要从根本上解决还是要从表结构设计的时候就要充分考虑数据热点的问题。