淘宝搜索的个性化离线实时分析系统Pora已升级至Pora2,Pora2是在基于Yarn的流式计算框架IStream基础上开发的,同时为保证数据和消息的实时处理系统中较多地使用了HBase,是一个典型的高并发读写HBase的分布式应用。
系统在发布之初遇到了比较严重的性能问题,表现为处理速度跟不上实时日志,并且整个Hadoop/HBase集群压力大,连带其它应用受影响。经过排查发现问题主要都出现在了对HBase的使用上,现将遇到的几个典型的使用HBase的问题总结如下,希望能为其它类似应用正确使用HBase提供参考。
HBase的Periodic Flusher
从系统的各种统计指标分析,系统主要慢在了读写HBase的环节,观察HBase日志发现每个RegionServer都在频繁地flush和compact。分析后发现当前hbase版本里有一个Periodic Flusher的机制,memstore中的数据如果持续一段时间没有flush的话hbase会自动触发flush,这个时间间隔默认是1小时。了解了一下这是hbase 0.94.8以后引入的新feature,初衷是防止有些memstore长时间不flush,在没有开启wal且遇到region server挂掉时导致数据丢失。
由于我们的 hbase每个region server有将近100个region,几乎每分钟都有region因为达到一小时的时间间隔触发flush,而多数情况下每次flush的文件都很小,flush次数多了之后又会引起compaction,如此频繁的flush和compaction使得region server处理速度明显变慢。在我们将这个配置调整为10小时后,可以从下图中看到hbase flush queue size和fs pread latency都有明显变小。
注:个人觉得hbase的这个新feature应用场景很有限,实在不应该作为一个默认开启的配置,建议可以通过配置直接禁用。
不间断的频繁scan对region server造成较大压力
Pora2采用了一个基于HBase实现的消息队列Hqueue,下游使用方通过读这个消息队列,第一时间获取最新的消息进行处理。
读消息的过程相当于一次Scan,在一开始的Pora2版本中我们未对读Hqueue的频率进行控制,导致某些读HQueue的worker不停地发起新的Scan,即便Hqueue数据已经读完,仍会立刻重新创建Scan,而这个Scan会因为读不到数据很快结束之后又重新创建。这样不停地新建Scanner对Region Server端构成了不小的且是不必要的压力。
发现这个问题后我们修改了这部分程序代码,在读完数据后sleep几秒后再重新scan。
在修改完hbase的配置并且加上读Hqueue的scan频率控制后,Pora2情况明显好转,但处理累积数据时的速度还是不够快。
超大并发下的hbase问题
从统计指标上分析,系统还是慢在访问hbase上,访问hbase的日志中不时出现各种TimeOutException也可以说明这一点。跟踪日志中某些频繁报超时的regionserver发现,有些region server日志持续报以下异常:
所在机器连接数很多,但load很低,而一旦pora2停了,这些现象就会很快消失。
从这一现象分析判断是Pora2对HBase的并发连接数太多了,使得region server的handler不够用,server端还没来得及处理请求,client端已经到了超时时间而断开。
为此我们大幅度减少了访问hbase的进程数以减少对hbase的并发连接,为了不降低处理能力,在进程内部使用更多的处理线程。由于线程间是共享对hbase的连接的,所以增加线程数不会增加对hbase的连接数。
通过这一调整,很大程度缓解了hbase region server的压力。
避免HBase访问热点
在作了较多优化改进后发现仍有几个worker比较慢,跟踪那几个慢的worker日志发现读HBase经常超时,找到超时的region server,从HMaster UI上观察到这个server的读写请求数明显是其它server的好几倍。开始怀疑是数据有倾斜,有热点region落到了这台机器上。在HBase UI上逐个检查Pora2用到的HBase表,果然在其中的一张表上发现它的第一个region的请求数比其它region高出一两个数量级。
按我们的设计预期,这个表的rowkey是加了hash前缀的,理论上不该有热点region,最终检查代码后才发现是生成rowkey的代码存在bug,生成前缀的代码用了 key.hashCode() % regionNum,结果有很多key的hashcode返回是负数,使得很多前缀是负数,全都落在了第一个region上。
而对hbase来说,一旦有一个region有热点,就会导致该region所在的region server变慢,进而使得这个server上其它表的region访问也慢,从而影响了整个hbase的性能。
Bulk load数据的Major Compaction
调查中还发现了一个Bulk load数据未执行Major Compaction引起的问题。
我们有一张表的数据是每天定时从HDFS中Bulk load到HBase里的,而且这张表的数据是只读的。结果几天下来后,因为没有数据写入,不会触发任何compaction,而每天都有新的HFile被Load进来,导致每个region下的HFile数越来越多,最终拖慢了这张表的读取性能。
发现问题后我们在每天的Bulk load结束后对这个表执行一次Major Compaction,有效解决了问题。
总结
高并发读写HBase的应用需要尽量保证对HBase的合理使用,不合理的使用有可能会导致某一个region server甚至整个hbase集群的性能出现问题,而hbase的性能问题又反过来使得所有应用性能下降,此时如果应用选择继续加大对hbase的并发访问,甚至有可能因此陷入一个性能继续变差的恶性循环。