byte[]做缓存key导致JVM异常

2022-09-29 17:18:22 浏览数 (2)

现象

产品的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;
        }
    });

在查询前后,发现缓存果真一次都没有命中

解决

  1. 我们的query压力不大,将这段无用的缓存模块删掉,这为我们节约了大量的内存
  2. query模块不是单点的,而且JVM随着时间的运行,会产生大量的碎片,从而影响性能。我们让query模块每天在凌晨的一个随机的时间点重启

0 人点赞