type
status
date
slug
summary
tags
category
icon
password
Property
Jun 14, 2023 05:31 PM

背景

技术背景:Nacos+SpringCloud+redis
某业务需要修改配置,由于需要频繁改动,因此选择配置上云;服务节点数:2+1
 

问题描述

灾难在点击发布配置的瞬间,一开始普罗米修斯疯狂报警,线程数飙升,此时下意识认为是配置写错,立即删除新配置进行发布。但报警还在继续,认为是配置没有正确刷新,遂在历史记录中选择回滚。但操作对濒死的服务无效,并且事故开始影响到其他业务。
 
经检查,此时服务无日志生成,已进入假死状态。受影响的服务仅一个,剩下两个服务正常。
 

补救措施

立即下线此服务,选择替补服务上线,经测试服务正常工作。同时开始对案发现场进行分析。
 

问题分析

工具:jca4614.jar
dump现场,拿出文件进行分析。
  1. cmd执行命令,打开可视化面板
    1. 点击左上角文件夹,选择.dump文件打开
    1. 可以从窗口中看到简要分析
      1. notion image
        其中有52%的线程处于Waiting on condition状态,对应方法为getBean()
    1. 右键点击文件,选择查看线程详情
      1. notion image
    1. 选择按堆栈深度排序,可以看到堆栈顶部的线程
      1. notion image
    1. 在右边查看详情
      1. notion image
        找到这里除框架外我们自己的代码,可以看到线程卡在了A类中的name()方法;
        这里粘贴部分代码如下:
         
        根据nacos动态配置刷新的过程可以判断,这里刷新动态配置后,销毁了相关的全部bean,此时业务走到B类,在初始化B类时,调用了这里的构造方法,从而用到了A类中的name()方法。
        但是name()方法仅返回了String,一般不会引起死锁,因此我们看向上一层堆栈。
         
        notion image
        很快定位到问题所在,这里看到在A类中有一个lock的动作,而这里lock是通过redis进行操作:
        这里完善一下A中的代码
        同时根据堆栈中信息可以得出,这里正在等待redissonClient.getLock(key).lock(time, TimeUnit.MILLISECONDS);
        此时基本可以定位到此线程卡死的位置了,再通过kibana查询日志得出,52线程早在凌晨三点便已卡住。
         

    现场重现

    1. 凌晨三点,52线程卡住,此线程由主线程新建,此时主线程仅等待2.5秒,因此52线程的卡住状态不会影响到线上业务。
    1. 修改配置,发出动态刷新指令;
    1. 此服务中开始销毁bean;
    1. 尽管A未被销毁,但已经被程序认为销毁成功;
    1. 新业务到达,开始重构bean;
    1. 在调用A.name()时,由于bean是单例,此实例被52线程持有,因此无法访问方法;
    1. 所有进程开始等待B类构建;
    1. 服务业务持续进入,消耗线程储备;
    1. 服务完全卡死不动。

    经验教训

    尽管使用了nacos进行动态刷新配置,但此操作仍然不安全,这里redis的操作有小概率没有获得返回结果,导致http超时等待。因此不建议频繁使用nacos进行配置刷新。
     
    Mysql数据导入问题考研失败后Notion给我的建议