arthas助力线上问题分析-线上实战篇

1 前言

2 基于heapAnalyzer和arthas的线上性能分析

2.1 需求背景

之前在客户现场,公司的大数据平台出现了页面访问卡顿的现象,经过诊断发现有内存泄漏问题,针对这次现象,对系统做一次全面的诊断。

2.2 现场保存

首先需要对线上平台的现场保存,依次执行以下几步。

第一步:ps -ef| grep datacompute 获取进程号${pid}

第二步:执行以下脚本(将内存、cpu以及网络环境生成镜像)

jmap -histo:live ${pid} > histo.snap

jmap -heap ${pid} > heap.snap

jmap -dump:live,format=b,file=dump.snap ${pid}
或者arthas的heapdump --live /home/admin/dump.hprof

jstack ${pid}> jstack.snap

lsof -p ${pid} -nP | grep TCP > alltcp.snap 

netstat -an | grep 8181 > netconn.snap

netstat -an | grep -i close > closewait.snap 

第三步:将应用全部日志文件拷贝出来。

第四步:重启应用

2.3 heapAnalyzer分析堆栈文件

  1. 下载
    wget https://public.dhe.ibm.com/software/websphere/appserv/support/tools/HeapAnalyzer/ha457.jar

  2. 运行
    java -Xmx8g -jar ha*.jar

打开dump文件后点击Leak Suspect查看内存泄露
在这里插入图片描述

其中泄露最大的为如上图所示的地方,可以看到hdfs中的FileSystem的Cache对象,里面96.3的内存消耗全来自与它,大约数百兆,定位问题后就马上开干!

2.4 源码分析

首先看FileSystem出问题的get方法,可以发现,默认不开disable.cache,逻辑走红框。

在这里插入图片描述

然后查看CACHE的get方法,继续定位到getInternal方法

在这里插入图片描述
在这里插入图片描述

查看代码,可以看到cache从map中取,根据Key类作为key,那就再看Key类的equals方法,如下图所示:

在这里插入图片描述

其中最可疑的是ugi,继续查找红框所示代码,成功就在眼前。

在这里插入图片描述

最后如图红框所示,如果指定了用户,每次都会构造一个新的Subject,因此计算出来的UserGroupInformation的hashcode也都不一样。这样也最终导致FileSystem的Cache不生效

知道问题后,把平台上所有使用filesystem的地方都加上close,然后客户端加上
fs.hdfs.impl.disable.cache 为 true
参数即可。

2.5 Arthas检测

修改完代码重新运行起来后,轮到Arthas闪亮登场了!我们将使用Arthas检测一下子变量的值,以印证修改是否成功。

这里使用watch来检测函数中变量的值,可以 参考之前的帖子《arthas助力线上问题分析-基本使用篇》中关于watch的使用。

在代码检测中,源码中需要检测静态内部类FileSystem.Cache中的map属性,如下图所示:

在这里插入图片描述

使用命令:

watch org.apache.hadoop.fs.FileSystem$Cache getInternal 'target.map.size()' -b -s

查看执行函数前后值,然后页面上点击触发。
其中target.map.size()为groovy表达式,查看map大小,也可以不加size,查看map中元素的值。

最后对比一下线上换包前后该变量大小区。

换包前
在这里插入图片描述

换包后

在这里插入图片描述

可以发现,map大小不增长了,bug修复。

3 问题代码热更新

尽管在生产环境热更新代码,并不是很好的行为,很可能导致:热更不规范,同事两行泪。

但很多时候我们的确希望能热更新代码,比如:

线上排查问题,找到修复思路了,但应用重启之后,环境现场就变了,难以复现。怎么验证修复方案?

又比如:

本地开发时,发现某个开源组件有bug,希望修改验证。如果是自己编译开源组件再发布,流程非常的长,还不一定能编译成功。有没有办法快速测试?

还记得开篇介绍线程cpu占用的那篇吗,如果需要线上动态修改可以继续往下操作。

3.1 查看类的classloaderhash

用sc命令查看需要热更新的类的hashcode

[arthas@3363085]$ sc -d com.hy.arthas.Problems | grep Hash
 classLoaderHash   238e0d81

3.2 修复代码

3.2.1 已有源码的修复

使用《arthas助力线上问题分析-基本使用篇》用的cpu问题的代码,将代码修改一下,添加一个睡眠时间,解决cpu消耗过高问题。加上Thread.sleep(10000)

/**
     * 模拟cpu高损耗
     */
    private static void cpu() {
        for (int i = 0; i < CPU_THREADS; i++) {
            executorService.submit(new Thread(() -> {
                while (true) {
                    cpurun();
                }
            }));
        }
    }

    /**
     * 只有完整退出的方法才可以被热更新
     */
    private static void cpurun() {
        log.info("cpu start");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

先执行mc命令将上传至服务器上的java文件编译成class文件。注:如果使用注解三方库等可能会造成失败。这种情况可以再本地编译完class文件再上传至服务器。

mc -c 238e0d81 /home/admin/zhy/arthastest/Problems.java -d /home/admin/zhy/arthastest

3.2.2 没有源码的修复

如果手上没有源代码,就先执行jad命令将class文件还原回java文件,修改后再编译成class文件上传服务器后再执行redefine。

jad --source-only com.hy.arthas.Problems > /home/admin/zhy/arthastest/Problems.java

3.3 热更新覆盖

执行redefine将class文件热更新至正在运行的程序中。

redefine -c 238e0d81 /home/admin/zhy/arthastest/Problems.class

在这里插入图片描述

查看本地的日志可以看到代码已被更新。

3.4 热更新注意点

注意:

  1. 函数参数格式不能修改,只能修改函数内部的逻辑
  2. 不允许新增加类的函数或变量
  3. 函数必须能够退出,如果有函数在死循环中,无法执行更新类。在例子中,只能修改cpurun方法中的内容,cpu方法因为是个死循环,无法修改。
    若进程重启,修改的代码会回到之前的版本

3.5 热更新总结

Arthas里 jad/mc/redefine 一条龙来线上热更新代码,非常强大,但也很危险,需要做好权限管理。

比如,线上应用启动帐号是 admin,当用户可以切换到admin,那么

  1. 用户可以修改,获取到应用的任意内存值(不管是否java应用)
  2. 用户可以attach jvm
  3. attach jvm之后,利用jvm本身的api可以redefine class
    所以:
  4. 应用的安全主要靠用户权限本身的管理
  5. Arthas主要是让jvm redefine更容易了。用户也可以利用其它工具达到同样的效果

4 springboot程序监控

虽然之前介绍了很多命令,比如trace/watch等,但是监控非静态的成员比较麻烦(ognl只能监控静态变量,如果理解有误麻烦留言指正)。

继续使用之前的例子,这里我们监控并操作成员变量fieldCache

public class ArthasService {
    private static List<String> cache = new ArrayList<>();
    //监控
    private List<String> fieldCache = new ArrayList<>();
  1. 首先启动一个timetunnel,并发送curl请求
    curl --location --request GET 'http://cdh219:3890/put' --form 'uid=3'

可以看到tt已经捕获到了,index为1000

[arthas@3625353]$ tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 65 ms.
 INDEX     TIMESTAMP                 COST(ms)     IS-RET     IS-EXP    OBJECT              CLASS                                  METHOD                                
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 1000      2021-01-19 16:54:48       5.819855     true       false     0x13a01d            RequestMappingHandlerAdapter           invokeHandlerMethod  
  1. 下一步拿到这个index,获取spring的ApplicationContext
[arthas@3625353]$ tt -i 1000 -w 'target.getApplicationContext()'
@AnnotationConfigServletWebServerApplicationContext[
    reader=@AnnotatedBeanDefinitionReader[org.springframework.context.annotation.AnnotatedBeanDefinitionReader@12f4f8e3],
    scanner=@ClassPathBeanDefinitionScanner[org.springframework.context.annotation.ClassPathBeanDefinitionScanner@37f5247f],
    annotatedClasses=@LinkedHashSet[isEmpty=true;size=0],
    basePackages=null,
    logger=@Slf4jLocationAwareLog[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog@3f98390c],
    DISPATCHER_SERVLET_NAME=@String[dispatcherServlet],
    webServer=@TomcatWebServer[org.springframework.boot.web.embedded.tomcat.TomcatWebServer@15aa7fed],
    ...
  1. 拿到ApplicationContext后基本上可以为所欲为了。。

比如 拿取值

[arthas@3625353]$ tt -i 1000 -w 'target.getApplicationContext().getBean("arthasService").fieldCache'
@ArrayList[
    @String[local:2],
    @String[local:3]
]

甚至添加/删除值

[arthas@3625353]$ tt -i 1000 -w 'target.getApplicationContext().getBean("arthasService").fieldCache.add("arthas test")'
@Boolean[true]

[arthas@3625353]$ tt -i 1000 -w 'target.getApplicationContext().getBean("arthasService").fieldCache.clear()'
@Boolean[true]

只能说,牛逼。。

5 修改spring的cron调度时间

5.1 需求背景

今天接到了研发帅哥的询问,想把线上的springboot程序内一个cron任务的执行周期改了,有没有可以热更新的方法。

5.2 模拟代码

先写一个测试类,其中cron的时间为10s一次。我们希望把cron间隔改成10s。

@Component
@Slf4j
@EnableScheduling
public class CronTest implements InitializingBean {
    @Override
    public void afterPropertiesSet() {}

    @Scheduled(cron="*/5 * * * * ?")
    public void cronTest() {
        log.info("cron: "+System.currentTimeMillis());
    }
}

5.3 源码流程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值