likes
comments
collection
share

如何在线上为所欲为

作者站长头像
站长
· 阅读数 26

概述

如果你看到这个标题想到的是 Arthas ,那么恭喜你,你想的跟我一样~

如果你看到这个标题一点想法的没有,那你可能真的需要系统性学习一下 arthas 了。arthas 是一款线上监控诊断工具,可以在不修改应用的情况下,对业务问题进行诊断。现在不用上几手 arthas ,都不好意思出门跟人调 bug了。

如果你想到的是 bpf,bcc 之类的,那你是真个大佬,大哥带带弟弟.jpg

准备工作

首先写一段简单的 java 代码,用来测试

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 求和测试类
 *
 * @author xisha
 * @date 2022/5/9 7:33 下午
 */
public class SumDemo {

    public static void main(String[] args) throws Exception {
        SumDemo demo = new SumDemo();
        while (true) {
            demo.test();
            TimeUnit.SECONDS.sleep(3);
        }
    }

    public void test() {
        Random random = new Random(System.currentTimeMillis());
        int numberSize = random.nextInt(5);

        int[] numbers = new int[numberSize];
        for (int i = 0; i < numberSize; i++) {
            numbers[i] = random.nextInt(10);
        }
        System.out.println(sum(numbers));
    }

    public Integer sum(int... numbers) {
        return Arrays.stream(numbers).sum();
    }

}

代码很简单,就是一直生成几个随机数,然后求和,跑起来的效果大概如下

如何在线上为所欲为

然后下载 arthas curl -O arthas.aliyun.com/arthas-boot…

更多下载方式见 arthas.aliyun.com/doc/install…

然后 java -jar arthas-boot.jar 选择我们的 SumDemo 进行 attach

成功之后的界面大概如下图

如何在线上为所欲为

常用功能

  1. 查看 sum 方法每次传入的参数都是啥。
  2. 修改sum方法的返回,使其返回一个固定的值。
  3. 查看 SumDemo 类的代码。

如果大佬对上面三个功能了如指掌,那么直接跳到下一章节即可。

watch 参数

watch SumDemo sum '{params,returnObj,throwExp}' -n 5 -x 3

如何在线上为所欲为

反编译

有时候我们想看看线上的代码是不是我们想要的,我们可以通过 jad 命令进行在线反编译。 jad SumDemo

效果大概如下图所示,我们可以可以方便的看到这个类的代码,类加载器,类文件的路径(这个在确定线上包的版本的时候十分有用)

如何在线上为所欲为

更多 jad 的用法可以查看 arthas.aliyun.com/doc/jad.htm…

热更新一条龙

现在让我们尝试修改 sum 函数的返回值,使其返回一个固定的值 public Integer sum(int... numbers) { return 49; //return Arrays.stream(numbers).sum(); } 改完之后通过 arthas 编译一个 class

首先我们需要找到 SumDemo 的类加载器的hash值 输入 sc -d *SumDemo , 对应的 classLoaderHash 就是我们需要的hash值,如图所示,我这边的 hash 值是 277050dc

如何在线上为所欲为

然后用这个类加载编译一下刚才改好的 java 文件 mc -c 277050dc ./SumDemo.java

如何在线上为所欲为

如上图所示,编译就算是成功了,如果不成功的话,可能是有些特殊情况,比如用了lombok,泛型对不上等等,要是失败了…… 那就算了,你跟这个命令莫得缘分。

然后我们使用 redefine 命令,

redefine classpath ,就可以热更新这个类了。

如何在线上为所欲为

如何在线上为所欲为

可以看到,热更新之后,我们的代码就只返回 49了。

注意,这里为什么先讲 jad 再讲 热更新呢? 因为 jad 命令跟 redefine 命令是冲突的,jad 会还原 redefine 产生的字节码,详见文档arthas.aliyun.com/doc/redefin…

为所欲为

上面讲了这么多,都是一些问题调试的功能,怎么就为所欲为了呢?

使用spring context 为所欲为

在 spring 的项目中,如果你有【保存 spring 上下文,以便自己下次使用】的习惯(如下代码),

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringBeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        SpringBeanUtil.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return (T) applicationContext.getBean(clazz);
    }

    public static Object getBean(String name) throws BeansException {
        return applicationContext.getBean(name);
    }
}

那么你可以用 arthas 获取这个上下文,然后再获取相应的bean,然后就可以为所欲为了。对应的 arthas 代码如下

ognl '#context=@SpringBeanUtil@applicationContext, #context.getBean("yourbeanName").doSomething()'

注意:加载对应的类可能需要对应的类加载器Hash,可以使用 sc -d *SpringBeanUtil 命令来获取类加载器 Hash,然后再使用 ognl 的时候加上 -c 选项即可。

使用 tt 为所欲为

如果没有【保存 spring 上下文】也不打紧,一般的 spring项目 都会有 http 接口(甚至健康检查的接口就是 http 接口),这个时候我们可以使用 tt 命令为所欲为。原理是找一个调用链路里有 spring 上下文的类,然后基于这个类记录这个请求,然后通过重放这个请求,达到执行任意 bean 代码的目的。比如 RequestMappingHandlerAdapter#invokeHandlerMethod 这个就很合适。具体过程如下:

我们先使用 tt 命令记录这个请求

tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod

然后等一个请求

如何在线上为所欲为

大概像这样,然后再用 tt 命令,-i 可以指定重放的请求,-w 可以执行ognl,然后为所欲为的代码大概如下

tt -i 1000 -w 'target.getApplicationContext().getBean('yourbeanName').doSomething()'

基于 dubbo 为所欲为

有人说我莫得 http 接口,全是 dubbo 的调用,那这个咋整?不慌的,dubbo 某个拓展里自动为我们保存了 spring 上下文,直接为所欲为即可。代码如下:

ognl '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, #context.getBean("yourBeanName").doSomeThing()'

vmtool 获取实例为所欲为

如果我的应用啥也没有,没有dubbo,甚至都不是 spring 的,那我要如何为所欲为呢? 这个时候我们可以使用 vmtool,直接获取对象进行为所欲为。当我看到这个工具的时候真是直呼好家伙!太强了简直! 以我们的SumDemo 为例,我们可以直接获取这个类进行函数调用,示例代码如下 vmtool --action getInstances --className SumDemo --express 'instances[0].test()'

如何监控开发的为所欲为?

我是个运维,我不想让开发老弟为所欲为,我要怎么办?关于这个问题,目前好像没有开源的实现,不过可以参考这个 github.com/alibaba/art… 自己整一把……

参考文档

【arthas 官方文档】 arthas.aliyun.com/doc/

【Arthas的一些特殊用法文档说明】 github.com/alibaba/art…

【当DUBBO遇上Arthas - 排查问题的实践】 github.com/alibaba/art…