60-微服务技术栈(高级):在线检测工具Arthas(实现CPU排查与代码热更新)

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 线上代码经常会出现CPU占用过高的情况,按以往经验我会使用top指令,进一步借助于jstack去查看具体信息从而进行问题排查,但基本上都逃不过需要重新发包的局面,即使是一个增量包,应用也需要短暂停启。后来运维大兄弟让我试一下Arthas,说是可以进行代码的热更新操作,正好来试一下。

1.前言

背景

线上代码经常会出现CPU占用过高的情况,按以往经验我会使用top指令,进一步借助于jstack去查看具体信息从而进行问题排查,但基本上都逃不过需要重新发包的局面,即使是一个增量包,应用也需要短暂停启。后来运维大兄弟让我试一下Arthas,说是可以进行代码的热更新操作,正好来试一下。关于Arthas的安装与基础使用可以参考我这两篇:

Arthas安装与监听SpringBoot应用

Arthas基础指令使用说明

环境

JDK1.8

SPringBoot 2.2.2

Arthas

Linux

测试代码:

@RequestMapping(value = "/bigThread")

@ResponseBody

public String bigThread(int id) {

   ArthasService.test();

   while (true) {

       Thread t2 = new Thread();

       t2.start();

       id ++;

       if(100000 == id) {

           return String.valueOf(id);

       }

   }

}

思路

2.thread -b 查看是否有阻塞线程

thread -b, 找出当前阻塞其他线程的线程,执行完之后并未发现,说明该线程并非一直阻塞,一直执行的

3.thread 查看占用最高的线程

   当thread之后不跟参数时,显示当前全部线程信息,我觉得 thread -n 10,展示前10应该就够用,可根据实际需要自己决定。

下图可以很直观的看出,我们的应用瞬间占用了77%的CPU(这里我是发起请求瞬间,通过thread查看的,所以比较直观,生产环境应该只有阻塞,死锁这种状态才会比较直观)

4.thread id 查看具体信息

在上一步基础上,我们进一步查看,thread 15(因为上面的ID=15)

他的大致意思就是:线程在等待一个条件从而继续执行,可以看到方法是在执行LinkedBlockingQueue.take方法时候,查看这个方法的API提示如下:

public E take() throws InterruptedException {

   E x;

   int c = -1;

   final AtomicInteger count = this.count;

   final ReentrantLock takeLock = this.takeLock;

   takeLock.lockInterruptibly();

   try {

       while (count.get() == 0) {

           notEmpty.await();

       }

       x = dequeue();

       c = count.getAndDecrement();

       if (c > 1)

           notEmpty.signal();

   } finally {

       takeLock.unlock();

   }

   if (c == capacity)

       signalNotFull();

   return x;

}

其中:AtomicInteger是保证高并发情况下的原子性,ReentrantLock标识可重入锁,都是JUC包下需要了解的这里不赘述,需要的百度了解下。

这段代码关键点就在于:notEmpty.await(),从队列中消费数据,当队列为空是,线程阻塞,所以我们大致知道现在出现的问题是线程阻塞,但是还是不知道具体哪行代码的问题。

如果能够明确知道这次更改了哪些代码,可以直接执行步骤6,不知道的话可以通过步骤5来定位问题。

5.watch 查看哪个Controller执行了代码

watch org.springframework.web.servlet.DispatcherServlet getHandler returnObj

这个脚本可以检测一切通过DispatcherServlet匹配Handler的方法,也就是进入Controller的请求,如下:

找到了对应的代码之后,我们来进一步观察异常信息,这里可能会有一个问题:就是我明明能通过日志去查看错误信息,为什么还需要这么繁琐的去操作。我的业务场景是:日志还是非常大的,刚捞到就被刷过去了,这时候定位日志不是很好操作,当然想捞下来日志肯定也是可以的,也很直观,我一般也都是去查看日志进行问题定位,这里也是提供一个思路。

6.watch 该方法异常信息

watch 类全路径 方法名 "{params[0],throwExp}" -e -x 2

如上,错误很直观的提示了出来,下面就可以修复解决了,这里我们也可以通过trace指令,查看执行时长:

trace 类全路径 方法名 "{params[0],throwExp}" -e -x 2

返回信息如下,也可以看到错误信息,和每个方法执行的时长

[arthas@10999]$ trace com.arthas.controller.OrderController bigThread

Press Q or Ctrl+C to abort.

Affect(class count: 1 , method count: 1) cost in 53 ms, listenerId: 10

`---ts=2020-08-19 14:45:57;thread_name=http-nio-0.0.0.0-8080-exec-10;id=16;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6

   `---[1452.684406ms] com.arthas.controller.OrderController:bigThread() [throws Exception]

       +---[0.168814ms] com.arthas.service.ArthasService:test() #20

       `---throw:java.lang.OutOfMemoryError #-2 [unable to create new native thread]

7.jad 反编译热更新

在上面知道问题之后,我们就来定位问题就好了,

命令:jad 类全路径 方法名

[arthas@13190]$ jad com.arthas.controller.OrderController


ClassLoader:                                                                                                                                                                                

+-org.springframework.boot.loader.LaunchedURLClassLoader@17f052a3                                                                                                                          

 +-sun.misc.Launcher$AppClassLoader@3d4eac69                                                                                                                                              

   +-sun.misc.Launcher$ExtClassLoader@45f45fa1                                                                                                                                            


Location:                                                                                                                                                                                  

file:/opt/software/arthas/Arthas.jar!/BOOT-INF/classes!/                                                                                                                                    


/*

* Decompiled with CFR.

*

* Could not load the following classes:

*  com.arthas.service.ArthasService

*  org.springframework.stereotype.Controller

*  org.springframework.web.bind.annotation.RequestMapping

*  org.springframework.web.bind.annotation.ResponseBody

*/

package com.arthas.controller;


import com.arthas.service.ArthasService;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;


@Controller

public class OrderController {

   @RequestMapping(value={"/bigThread"})

   @ResponseBody

   public String bigThread(int id) {

       ArthasService.test();

       do {

           Thread t2 = new Thread();

           t2.start();

       } while (100000 != ++id);

       return String.valueOf(id);

   }

}


Affect(row-cnt:1) cost in 1513 ms.

此时代码就被反编译了,为了能够更改,所以我们需要输出为java文件

指令:jad com.arthas.controller.OrderController > /tmp/OrderController.java

即:jad 类全路径 方法名 > 存储路径/存储名称

然后到tmp路径下vi修改java文件即可,修改完成之后,查看对应的classloader为编译做准备

sc -d *OrderController | grep classLoaderHash

mc -c 17f052a3 /tmp/OrderController.java -d /tmp

但是这里编译出错了,官方提示:

所以我们本地编译好class文件,上传上去是一样的

编译前调用

[arthas@13190]$ trace com.arthas.controller.OrderController bigThread

Press Q or Ctrl+C to abort.

Affect(class count: 1 , method count: 1) cost in 77 ms, listenerId: 2

`---ts=2020-08-19 15:51:46;thread_name=http-nio-0.0.0.0-8080-exec-1;id=d;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6

   `---[6734.666529ms] com.arthas.controller.OrderController:bigThread() [throws Exception]

       +---[0.786517ms] com.arthas.service.ArthasService:test() #20

       `---throw:java.lang.OutOfMemoryError #-2 [unable to create new native thread]

更新前代码

@RequestMapping(value = "/bigThread")

@ResponseBody

public String bigThread(int id) {

   ArthasService.test();

   while (true) {

       Thread t2 = new Thread();

       t2.start();

       id ++;

       if(100000 == id) {

           return String.valueOf(id);

       }

   }

}

更新后代码

@RequestMapping(value = "/bigThread")

@ResponseBody

public String bigThread(int id) {

   ArthasService.test();

   Thread t2 = new Thread();

   t2.start();

   return "success";

}

编译指令

[arthas@13190]$ redefine /tmp/OrderController.class

redefine success, size: 1, classes:

com.arthas.controller.OrderController

编译后调用三次

`---ts=2020-08-19 15:52:02;thread_name=http-nio-0.0.0.0-8080-exec-3;id=f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6

   `---[5.609405ms] com.arthas.controller.OrderController:bigThread()

       `---[0.204675ms] com.arthas.service.ArthasService:test() #20


`---ts=2020-08-19 15:52:04;thread_name=http-nio-0.0.0.0-8080-exec-4;id=10;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6

   `---[3.900149ms] com.arthas.controller.OrderController:bigThread()

       `---[0.14636ms] com.arthas.service.ArthasService:test() #20


`---ts=2020-08-19 15:52:04;thread_name=http-nio-0.0.0.0-8080-exec-5;id=11;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6

   `---[1.90945ms] com.arthas.controller.OrderController:bigThread()

       `---[0.147353ms] com.arthas.service.ArthasService:test() #20

可以发现时间从6734.666529ms变成3ms左右,说明热更新的代码生效了

8.profile 绘制火焰图做后续分析

📎20200819-102814.svg 附件如下图:

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
5月前
|
消息中间件 监控 Java
微服务架构深入理解 | 技术栈
微服务架构深入理解 | 技术栈
201 0
|
11月前
|
Java Linux
linux中找到最耗CPU的那段Java代码
linux中找到最耗CPU的那段Java代码
|
11月前
|
Arthas 测试技术
Arthas排查生产环境CPU飚高问题
Arthas排查生产环境CPU飚高问题
152 0
Arthas排查生产环境CPU飚高问题
|
1月前
|
调度
CPU调度器实现提示:针对特定体系结构代码【ChatGPT】
CPU调度器实现提示:针对特定体系结构代码【ChatGPT】
|
2月前
|
Java 数据库连接 Nacos
SpringCloud微服务配置管理、配置热更新
SpringCloud微服务配置管理、配置热更新
58 0
|
2月前
|
Kubernetes Nacos 微服务
【技术难题破解】Nacos v2.2.3 + K8s 微服务注册:强制删除 Pod 却不消失?!7步排查法+实战代码,手把手教你解决Nacos Pod僵死问题,让服务瞬间满血复活!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但有时会遇到“v2.2.3 k8s 微服务注册nacos强制删除 pod不消失”的问题。本文介绍此现象及其解决方法,帮助开发者确保服务稳定运行。首先需检查Pod状态与事件、配置文件及Nacos配置,确认无误后可调整Pod生命周期管理,并检查Kubernetes版本兼容性。若问题持续,考虑使用Finalizers、审查Nacos日志或借助Kubernetes诊断工具。必要时,可尝试手动强制删除Pod。通过系统排查,通常能有效解决此问题。
50 0
|
3月前
|
机器学习/深度学习 TensorFlow API
Keras是一个高层神经网络API,由Python编写,并能够在TensorFlow、Theano或CNTK之上运行。Keras的设计初衷是支持快速实验,能够用最少的代码实现想法,并且能够方便地在CPU和GPU上运行。
Keras是一个高层神经网络API,由Python编写,并能够在TensorFlow、Theano或CNTK之上运行。Keras的设计初衷是支持快速实验,能够用最少的代码实现想法,并且能够方便地在CPU和GPU上运行。
|
3月前
|
缓存 Devops 微服务
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
|
4月前
|
并行计算 异构计算 Python
python代码torch.device("cuda:0" if torch.cuda.is_available() else "cpu")是什么意思?
【6月更文挑战第3天】python代码torch.device("cuda:0" if torch.cuda.is_available() else "cpu")是什么意思?
298 4
|
5月前
|
负载均衡 Nacos 数据库
【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置
【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置
116 1