现象
产品的query模块运行一段时间后,就不能正常提供服务,严重影响了服务的可用性
追查
查看日志,发现读取Hbase时发生了OutOfMemory现象。 首先获取JVM的进程号,为16796 jstat -gcutil 16796 发现频繁的发生full gc,显然full gc没有将内存清理掉
$jmap -histo 16796 查看进程中的对象,发现包com.fasterxml.jackson.databind.node引入的对象格外多(包括包中的对象以及引用的[C、[B等对象)。 然后查看代码,发现代码中引入了缓存。我们查询的数据源是HBase,当时的作者为了减少和HBase之间的IO交互,将查询的内容进行了缓存,而key是HBase的(byte[])rowkey。想法是好的,但实际上起了负面作用。查询缓存的第一步是检查key是否在缓存中,该过程首先判断key的hashcode是否能和已有的key匹配,如果匹配,再判断是否equals。因为数组的hashcode和对象的地址有关,而和内容是没有关系的,这样缓存永远都不会命中。
代码语言:javascript复制 LoadingCache<byte[], ArrayNode> rowKey2ArrayNodeCache = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_NUMBER)
.expireAfterWrite(CRASH_CACHE_DURATION, TimeUnit.HOURS)
.build(new CacheLoader<byte[], ArrayNode>() {
@Override
public ArrayNode load(byte[] key) throws Exception {
return EMPTY_NODE;
}
});
在查询前后,发现缓存果真一次都没有命中
解决
- 我们的query压力不大,将这段无用的缓存模块删掉,这为我们节约了大量的内存
- query模块不是单点的,而且JVM随着时间的运行,会产生大量的碎片,从而影响性能。我们让query模块每天在凌晨的一个随机的时间点重启