美文网首页
cassandra驱动故障定位记录

cassandra驱动故障定位记录

作者: 定金喜 | 来源:发表于2023-01-18 14:12 被阅读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();
            }
        }
    

    相关文章

      网友评论

          本文标题:cassandra驱动故障定位记录

          本文链接:https://www.haomeiwen.com/subject/htyxhdtx.html