cassandra驱动故障定位记录

2023-01-18  本文已影响0人  定金喜

问题描述

客户反馈说应用运行了一段时间后,页面突然打不开了,运维说是cpu很高,而且日志有OOM内存不足,刚开始以为是内存不够,将这个客户的应用最大内存double之后,运行可一段时间,又出现了同样的问题,怀疑可能存在内存泄漏和其他问题,所以进行了分析定位。

问题定位过程

根据反馈的高CPU问题,按照传统方法进行了定位:
出现问题的原因:内存不够,JVM一直在fullgc,一秒可能有数十次fullgc
排查步骤
1.定位出高cpu的线程
进入容器,top -Hp 1(因为是在docker容器内,默认的应用pid就是1)


线程

隔一段时间再打印一下



cpu比较高的线程PID大概在10-26之间
2.printf ’%x\n' 10~26
打印出16进制的线程号
3.jstack 1 |grep 线程号,可以发现耗cpu的线程为gc线程

4.为了确认是gc引起的问题

jstat -gc 1



fullgc此处达到10万次+,说明大部分时间都消耗在了fullgc上面,而且年轻代和老年代内存都占满了,也没有释放。

解决思路:
临时解决
客户的最大内存设置为4G,目前来看是太小,这个客户的内存大小可以设置为8G。

但是为什么会出现内存不足的情况,通过测试环境测试,业务代码中没有出现内存泄漏的可能,基本上minorgc就可以将内存给释放掉,不可能出现内存不足的可能性,所以可能是框架代码出现了问题,导致了内存泄漏;从这个思路出发,将客户环境当前的内存情况进行了jmap,输出堆转储文件,然后将这个问题下载到本地进行分析。

使用JDK自带的visualVM进行堆转储文件进行分析


其中有几个类型占据了绝大部分内存,char[] 、String和LinkedHashMap,然后分别对这几部分进行分析,char[]大部分数据是这样的



抽丝剥茧后发现,这些字符串数据被DefaultNode这个类对象所引用,这个类有个成员变量rawTokens。这是Cassandra驱动里面的一个类,项目中的驱动依赖。

<dependency>
       <groupId>org.springframework.data</groupId>
       <artifactId>spring-data-cassandra</artifactId>
       <version>3.3.0</version>
 </dependency>

查看下DefaultNode类源码:



然后我们搜一下DefaultNode


上面红圈是DefaultNode实例被引用的地方有三个地方

LoadBalancingPolicyWrapper的distances变量


此处的map是hashmap

ControlConnection#SingleThreaded成员变量



此处的map是weakhashmap
hashmap和weakhashmap的不同,参见强引用和弱引用的不同,weakhashmap如果key没有被强引用所引用,GC的时候就会释放掉,不管内存是否够用都会释放,但是因为该key又被强引用所把持,所以此处的weakhashmap是没有意义的,不会被释放

写一个例子说明下这两个引用的区别:

public static void main(String[] args) throws Exception{

        AppConfig appConfig = new AppConfig();
        Map<AppConfig,String> weakHp = new WeakHashMap<>();
        weakHp.put(appConfig, "22333");

        Map<AppConfig,String> strongHp = new HashMap<>();
        strongHp.put(appConfig,"233443222");

        appConfig = null;

        System.gc();

        Thread.sleep(10000L);

        System.out.println(weakHp.size());

        System.out.println(strongHp.size());
    }

输出为
1
1
因为被key强引用的map所使用,所以此处虽然声明为弱引用也没有用

到此,问题的原因大概清楚了,cassandra驱动存在内存泄漏,创建了很多的DefaultNode对象,而且无法释放,但是为什么会创建如此多的DefaultNode对象呢,测试环境好像没出现此类问题,为了找到这个根源,继续分析

查看客户的线上日志发现,有段时间warn日志疯狂输出


有32个线程每隔10几秒就输出了这些日志,我怀疑这段时间可能cassandra出现了问题,然后疯狂创建了DefaultNode,而且应该回收的DefaultNode又没法回收,造成了问题,然后去网上搜索了下
https://www.coder.work/article/7883970

这个问题貌似与我们遇到的问题有点类似,但是目前解决方案好像也没有,官方提供的方案没有效果,还是会出现问题

解决方案

1.这本身是该cassandra驱动存在的内存泄漏的bug,已经向开源项目组提交问题,希望他们能及时修改此问题;

https://datastax-oss.atlassian.net/browse/JAVA-3051

2.定时任务定时清理掉多余的****DefaultNode

目前主要内存泄漏在于LoadBalancingPolicyWrapper#distances,写个定时任务,清理掉多余的数据,代码

/**
     * 清理cassandra驱动导致的内存泄漏问题
     * @throws Exception
     */
    @Scheduled(cron = "0 */10 * * * ?")
    public void clearDefaultNodes() throws Exception{

        Map<String, LoadBalancingPolicy> loadBalancingPolicies
                = cqlSession.getContext().getLoadBalancingPolicies();

        Field field = DefaultLoadBalancingPolicy.class.getSuperclass().getDeclaredField("context");
        field.setAccessible(true);
        InternalDriverContext internalDriverContext
                = (InternalDriverContext)field.get(loadBalancingPolicies.get("default"));

        Field distancesField = LoadBalancingPolicyWrapper.class.getDeclaredField("distances");
        distancesField.setAccessible(true);
        LoadBalancingPolicyWrapper loadBalancingPolicyWrapper = internalDriverContext.getLoadBalancingPolicyWrapper();
        Map<Node, Map<LoadBalancingPolicy, NodeDistance>> distances
                = (Map<Node, Map<LoadBalancingPolicy, NodeDistance>>)distancesField.get(loadBalancingPolicyWrapper);

        //当distances为空或者distances数量小于10000不处理,超过10000认为是有异常的
        if (MapUtils.isEmpty(distances)) {
            return;
        }

        int distancesSize = distances.size();
        logger.info("distances size is {}", distancesSize);
        if (distancesSize <=10000) {
            return;
        }

        Field distancesLockField = LoadBalancingPolicyWrapper.class.getDeclaredField("distancesLock");
        distancesLockField.setAccessible(true);
        Lock lock = (Lock) distancesLockField.get(loadBalancingPolicyWrapper);
        lock.lock();
        try {
            distances.clear();
        }finally {
            lock.unlock();
        }
    }
上一篇下一篇

猜你喜欢

热点阅读