记一次HBase内存泄漏导致RegionServer挂掉问题

问题描述

在测试Phoenix稳定性时,发现HBase集群其中一台RegionServer节点FullGC严重,隔一段时间就会挂掉。

HBase集群规格

选项 Master RegionServer
节点数 2台,一主一备 3台
CPU核数 2core 4core
存储 SSD云盘,单节点440G

初步分析

使用jstat监控RegionServer的Heap size和垃圾回收情况:

[root@iZbp18zqovyu9djsjb05dzZ ~]# jstat -gcutil 454 5000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
100.00   0.00  55.68  90.19  98.75  97.30   2244   57.381    149    3.411   360.792
100.00   0.00  55.84  91.32  98.75  97.30   2244   57.381    150    4.724   365.516
100.00   0.00  56.30  92.22  98.75  97.30   2244   57.381    150    4.724   365.516
100.00   0.00  56.88  91.55  98.75  97.30   2244   57.381    151    3.411   368.927
100.00   0.00  57.09  92.37  98.75  97.30   2244   57.381    152    4.057   372.984

OLD区内存一直在90%多,且FullGC次数一直在增多。

通过ganglia查看集群的FullGC情况,也可以看出003节点持续在FullGC,并最终挂掉。
image

怀疑存在内存泄漏导致FullGC可回收的内存越来越小,回收的时间也越来越长,最终导致RegionServer心跳超时,被Master干掉。

问题定位

排查内存泄漏问题,可借助jmap分析java堆内存的占用情况,jmap使用参考:https://blog.csdn.net/xidiancoder/article/details/70948569

使用jmap将RegionServer堆内存dump下来:

[root@iZbp16ku9i9clejitib6dzZ opt]# jmap -dump:format=b,file=regionserver.bin 30590
Dumping heap to /opt/regionserver.bin ...
Heap dump file created

然后借助MAT内存分析工具分析RegionServer的堆内存占用情况:
image

MAT提供了Memory Leak Suspect Report分析功能,可帮助我们找到可能存在泄漏的对象:
image
可以看到Configuration对象已经占用到80%堆内存,Configuration底层使用HashTable存配置的键值对,与上图内存分配相符,但是还不确定这么多对象是哪里来的以及为什么没有被回收。

查看内存泄漏详细信息:
image
看到这有点懵,Configuration与FSHLog有啥关系呢,这时就要结合Configuration的引用链和HBase的源码来看了。
在堆内存Histogram中,过滤到Configuration对象,然后Merge Shortest path to GC Roots:
image
观察到GCRoot最多的就是FSHLog,查看引用链:
image
FSHLOG引用了OpenRegionHandler$PostOpenDeployTasksThread线程对象,进一步引用了HRegion,HRegion引用了Coprocessor,然后引用Configuration对象,导致内存泄露。

结合FSHLog源码

 /**
   * Map of {@link SyncFuture}s keyed by Handler objects.  Used so we reuse SyncFutures.
   * TODO: Reus FSWALEntry's rather than create them anew each time as we do SyncFutures here.
   * TODO: Add a FSWalEntry and SyncFuture as thread locals on handlers rather than have them
   * get them from this Map?
   */
  private final Map<Thread, SyncFuture> syncFuturesByHandler;
  
  ...
   
  private SyncFuture getSyncFuture(final long sequence, Span span) {
    SyncFuture syncFuture = this.syncFuturesByHandler.get(Thread.currentThread());
    if (syncFuture == null) {
      syncFuture = new SyncFuture();
      this.syncFuturesByHandler.put(Thread.currentThread(), syncFuture);
    }
    return syncFuture.reset(sequence, span);
  }

syncFuturesByHandler缓存了写WAL的每个线程,但是在线程结束时并不会清除Map中缓存的线程,导致引用的Configuration对象不会被释放。

问题修复

目前该问题已有阿里云HBase社区commiter正研修复,并贡献给社区:https://issues.apache.org/jira/browse/HBASE-21228

猜你喜欢

转载自yq.aliyun.com/articles/658414