由于3月份换了电脑,更新博客的事情就耽搁下来,今天利用周六在家的空闲时间,在Mac上安装了hexo,总算能够正常更新博客了。
最近做了几个数据量较大的项目,在Spark读写HBase时遇到了一系列的性能问题,造成Spark程序不能正常运行,甚至出现Spark任务运行十几个小时最终报异常的情况,最终经过对HBase表的调整和Spark程序的优化,实现了性能的显著提升。下面主要讲解一下整个过程中遇到的一些问题,以及最终的解决方法。
1. HBase没有预分区导致RegionServer卡死的问题
问题描述:之前考虑到HBase表新建完默认只有一个Region,当数据量达到阈值时会自动进行Region Split操作。所以系统设计之初并没有在新建HBase表时进行预分区操作。结果当大量数据一次导入HBase时,由于Region Split并不能及时触发,造成写入压力集中在一个Region上,导致RegionServer单点负载过高,阻塞甚至卡死的问题,Spark日志会打印connection reset异常(即由于RegionServer卡死造成connection reset),Spark任务会一直阻塞。
解决方案:针对这个问题,我们在建立HBase时通过SPLITS_FILE指定分区文件,在分区文件中划定每个region的范围。我们首先在HBase的Rowkey加入了Hash前缀,并指定了00~ff共256个Region。具体的预分区方法,可以参考博客:HBase建表时预分区的方法
2. 数据倾斜导致的HBase读写性能较差的问题
问题描述: 系统设计之初对数据倾斜考虑不到位,因此没有对Rowkey加入Hash前缀,导致HBase Region读写请求严重不均衡,造成Spark读写HBase性能较慢。
解决方案: 写入HBase时对RowKey添加Hash前缀,结合HBase表的手动预分区,可以使数据均匀分布于HBase集群的各Region中,避免数据倾斜造成HBase单点负载过高,出现单点瓶颈。RowKey添加Hash前缀可以参考org.apache.hadoop.hbase.util.MD5Hash类
。
3. Spark BulkLoad方式向HBase导入数据HFile频繁拆分的问题
问题描述:为了提升Spark写入HBase的性能,系统设计采用了BulkLoad的方法,绕过绕过HBase的WAL机制,直接将生成的HFile文件导入HBase集群,但观察Spark任务发现两点问题:
- BulkLoad方式下会将HDFS上生成的HFile文件加载到HBase集群中,若某个HFile中的数据属于不同Region,会在HDFS中将该HFile拆分为多个HFile文件,可以在spark日志中发现大量的HDFS文件路径频繁split的打印信息,对写入HBase性能影响很大。
- HBase要求每个HFile内的RowKey为升序排列,Spark采用BulkLoad方式在生成HFile时需要对数据进行排序,否则会导致写入HBase失败。在排序过程中,系统一开始使用SortByKey全局排序算子进行排序,这个过程性能较慢;
解决方案:Spark在生成HFile之前,使用Spark的PartitionBy算子进行自定义分区,保证每个HFile文件中的数据属于同一个Region,可以避免上面HDFS 频繁拆分HFile现象的发生。 针对SortByKey算子运行较慢的问题,由于只需要保证每个HFile内部有序即可,我们使用MapPartition算子进行局部排序,替换原有的SortByKey全局排序类算子,提高了排序性能。
4. 总结
通过这次的调优过程,最大的体会就是,上述的大部分问题在系统设计之初本就可以避免,但当初过于乐观,想着HBase会自动进行Region Split,系统中的数据也不会过于倾斜,这些估计或预想都是毫无根据,纯粹是一厢情愿,也就造成了系统正式上线后遇到了诸多性能问题,这次是非常深刻的教训,今后在系统设计时,要本着最严格的要求进行开发!