自学内容网 自学内容网

【进阶篇】五、Java Agent实现系统数据采集


根据Java Agent静态和动态模式的特点,动态模式更适配Arthas这类随时连接Java程序的工具,动态模式则更适配Java程序启动之后就需要持续地进行信息的采集的场景,如Application performance monitor (APM) 应用程序性能监控系统(Zipkin、Sky walking)采集数据

1、APM系统数据采集

实现需求:

  • 无侵入式采集springboot应用中,controller层方法的执行耗时
  • 将采集到的数据写入到文件中(以后写入到ES、Sky Walking等)

使用Byte Buddy字节码增强框架,搭配Java Agent静态代理:

public class AgentMain {

    public static void premain(String agentArgs, Instrumentation inst) {
        //使用byte buddy增强类
        new AgentBuilder.Default()
                //禁止byte buddy处理时修改类名
                .disableClassFormatChanges()
                //处理时使用retransform增强
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                //打印出增强过程中的错误日志(官网固定写法)
                .with(new AgentBuilder.Listener.WithTransformationsOnly(AgentBuilder.Listener.StreamWriting
                        .toSystemOut()))
                //匹配哪些类,这里要处理Controller类的方法,所以匹配有@RestController或者@Controller注解的
                .type(ElementMatchers.isAnnotatedWith(
                        ElementMatchers.named("org.springframework.web.bind.annotation.RestController")
                                .or(ElementMatchers.named("org.springframework.web.bind.annotation.Controller"))))
                //增强,使用MyAdvice通知,ElementMatchers.any()即对所有方法都进行增强
                .transform((builder, classLoader, module, protectionDomain) ->
                        builder.visit(Advice.withCustomMapping()    //代表需要传递参数到Byte Buddy
                                .bind(AgentParam.class, agentArgs)  //将静态代理传入的参数通过注解绑定
                                .to(TimeAdvice.class).on(ElementMatchers.any())))
                .installOn(inst);
    }
}

自定义通知类,描述如何增强:

import net.bytebuddy.asm.Advice;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class TimeAdvice {

    //方法进入时,返回开始时间
    @Advice.OnMethodEnter
    static long enter(@Advice.AllArguments Object[] ary) {
        //返回毫秒值
        return System.nanoTime();
    }

    /**
     * 方法退出时候,统计方法执行耗时
     * Advice.Origin传入#t代表获取增强的类的类名,#m即方法名
     */
    @Advice.OnMethodExit
    static void exit(@Advice.Enter long value,
                     @Advice.Origin("#t") String className,
                     @Advice.Origin("#m") String methodName,
                     //让用户能指定信息写入文件的文件名,默认agent.log
                     @AgentParam("agent.log")String fileName) {
        String str = methodName + "@" + className + "耗时为: " + (System.nanoTime() - value) + "纳秒\n";
        try {
            FileUtils.writeStringToFile(new File(fileName), str, StandardCharsets.UTF_8, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上面写入文件用apache的common-ios,相关依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

此外,为了获取到用户运行Java Agent的Jar包时传入的参数,定义了@AgentParam注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AgentParam {
    String value();
}

最后,Java Agent的说明文件:

Manifest-Version: 1.0
Premain-Class: com.llg.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true

maven-assembly-plugin插件打出Java Agent的Jar包

2、效果

运行普通的Java应用,指定Java Agent的Jar包:

在这里插入图片描述
调用接口:

在这里插入图片描述

增强成功,数据采集:

在这里插入图片描述

3、注意点

Java Agent中,需要传递参数到Byte Buddy,可绑定Key Value,Key是一个自定义注解,Value是参数的值

在这里插入图片描述

自定义注解:

在这里插入图片描述
通过注解注入参数:

在这里插入图片描述

4、总结

  • APM系统用到了Java Agent的静态代理模式 + 字节码增强,从而采集到方法的耗时、数据库的查询时长、SQL信息等
  • Arthas用到了Java Agent的动态代理模式,用到了JMX获取到一些信息,以及字节码增强打印方法耗时、参数等

原文地址:https://blog.csdn.net/llg___/article/details/137840520

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!