• 线上故障排查 | 烂代码引发的血案


    烂代码引发的血案

    场景

    以下有一段烂代码来自真实项目场景,如下:

    public synchronized void savePhotos(String photoUrl,String userId){
            final Photo photo = new Photo();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //赋值
                    photo.setUrl(photoUrl);
                    photo.setUserId(userId);
                    //保存图片信息入库
                    photoService.save(photo);
    
                    //下载图片到本地服务器
                    photoService.downloadFileByUrl(photoUrl);
    
                }
            }).start();
    public synchonized void downloadFilesByUrl(String photoUrl) {
       ........
    
       httpClient.execute();//执行远程调用
       ......
    }

    这个savePhoto方法主要功能是保存图片信息到数据库并且下载图片到本地服务器,用户端每次请求当前服务,都会调用一次savePhoto方法(用户每次请求,当前容器都会启动一条线程去处理)。

    注意:photoService.downloanFileByUrl(photoUrl)里面使用到httpClient,没有设置超时时间时,有可能出现无限等待,并且downloanFileByUrl方法上也加了synchonized关键字,内部也没有再开启其他线程,直接就是远程下载图片的操作

    问题:

    1.请列举该代码可能出现的隐患?
    2.假设执行photoService.downloanFileByUrl 方法时,由于该方法下载图片时出现异常,并且该下载方法没有设置超时,导致了一直处于超时无响应,那么会发生什么问题?
    3.通过系统监控可以看到此时线程数不断上升,假设你从来没有接触过这个项目,请提出一个方案去排查引起该问题的原因。

    解决思路:
    1.对于第一题,出现的隐患,首先从方法第一行开始观察
    synchonized关键字是否必要?
    方法内是否存在共享变量?
    是否有必要每次创建一条线程去处理?
    当downloanFileByUrl挂起时,会出现什么问题?

    2.对于第二题解决思路
    从synchonized关键字入手,该关键字会把整个方法锁住,所有线程都会等待(但是对于方法内部开启线程的情况,不会引起阻塞)

    3.对于第三题解决思路
    想想分析线程的工具有哪些

    个人思路:
    1.对第一题,有以下隐患:
    当前方法并没有任何共享变量,synchonized会导致整个方法锁住,同一时间只允许一条线程进入,其他线程都会等待,当并发量大时会影响系统吞吐(但上面的代码synchonized关键字对于savePhotos方法不会影响吞吐,因为其内部单独又开启了线程),synchonized关键字可以直接去掉

    当调用photoService.downloadFileByUrl(photoUrl)方法时,因为每次都new Thread,这样会导致每次请求都会创建一条线程去处理,而每一条线程都会阻塞在下载图片的方法,当某一次调用其底层使用的httpClient下载图片挂起时,当前该线程会阻塞在downloadFileByUrl(photoUrl)里不能释放资源(因为有synchonized关键字,并且其内部没有再开启线程),这会导致后续所以创建的线程都会阻塞在该方法,从而引起线程膨胀,最终造成系统宕机,如果确认downloadFileByUrl里没有共享成员变量,需要把downloadFileByUrl方法上synchonized去掉,因为它将会是引起线程阻塞的万恶之源

    每次请求都new Thread也会造成线程资源浪费,可以考虑使用线程池代替

    2.对第二题,可能会出现以下问题:

    所有线程阻塞在downloadFileByUrl方法上,线程数不断上升,从而导致宕机

    3.对于第三题,可以使用如下方案去排查:
    a. 第一步先找出Java进程ID,可通过如下命令

    ps -ef | grep 应用名 | grep -v grep

    b. 假设当前进程ID是2110,再利用jstack分析该PID,很快我们就找到了一个线程处于WAITING状态,查看堆栈信息,找到对应的代码即可

    jstack 2110

    也可以通过ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid去找出消耗CPU最高的线程,假设线程ID是21742,然后使用printf “%x ” 21742,打印其16进制数值为54ee(因此jstack线程日志nid用16进制表示),再通过 jstack 2110 | grep 54ee 就可以找到最消耗CPU的线程堆栈信息

  • 相关阅读:
    spring的9个地方调用了5次后置处理器的详细情况
    spring容器启动
    什么是好的代码
    随机文件读写
    mysql 写锁
    mysql 高效率查询背景
    spring中的重点
    spring bean生命周期和上下文初始化
    雷电模拟器 v3.71绿色版
    免费申请 QQ 免费靓号
  • 原文地址:https://www.cnblogs.com/evan-liang/p/12233935.html
Copyright © 2020-2023  润新知