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

烂代码引发的血案

场景

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

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\n” 21742,打印其16进制数值为54ee(因此jstack线程日志nid用16进制表示),再通过 jstack 2110 | grep 54ee 就可以找到最消耗CPU的线程堆栈信息

猜你喜欢

转载自blog.csdn.net/evan_leung/article/details/80879446