Skywalking介绍

Skywalking是一个国产的开源框架,2015年有吴晟个人开源,2017年加入Apache孵化器,国人开源的产品,主要开发人员来自于华为,2019年4月17日Apache董事会批准SkyWalking成为顶级项目,支持Java、.Net、NodeJs等探针,数据存储支持Mysql、Elasticsearch等,跟Pinpoint一样采用字节码注入的方式实现代码的无侵入,探针采集数据粒度粗,但性能表现优秀,且对云原生支持,目前增长势头强劲,社区活跃。
Skywalking是分布式系统的应用程序性能监视工具,专为微服务,云原生架构和基于容器(Docker,K8S,Mesos)架构而设计,它是一款优秀的APM(Application Performance Management)工具,包括了分布式追踪,性能指标分析和服务依赖分析等。

链路追踪框架对比

目前市面上开源的APM系统主要有CAT、Zipkin、Pinpoint、SkyWalking,大都是参考Google的Dapper实现的

功能和技术方案对比

  • Zipkin是Twitter开源的调用链路分析工具,目前基于Spingcloud sleuth得到了广泛的应用,特点是轻量,部署简单。
  • 一个韩国团队开源的产品,运用了字节码增强技术,只需要在启动时添加启动参数即可,对代码无侵入,目前支持Java和PHP语言,底层采用HBase来存储数据,探针收集的数据粒度非常细,但性能损耗大,因其出现的时间较长,完成度也很高,应用的公司较多
  • Skywalking是本土开源的基于字节码注入的调用链路分析以及应用监控分析工具,特点是支持多种插件,UI功能较强,接入端无代码侵入。
  • CAT是由国内美团点评开源的,基于Java语言开发,目前提供Java、C/C++、Node.js、Python、Go等语言的客户端,监控数据会全量统计,国内很多公司在用,例如美团点评、携程、拼多多等,CAT跟下边要介绍的Zipkin都需要在应用程序中埋点,对代码侵入性强。

首先说一下Javaagent是什么。Javaagent是一种能够在不影响正常编译的情况下,修改字节码的技术。JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。
javaagent是java命令的一个参数,参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:

  1. 这个 jar 包的MANIFEST.MF 文件必须指定 Premain-Class 项。
  2. Premain-Class 指定的那个类必须实现 premain()方法。

JavaAgent 实际应用

  1. 可以在加载java文件之前做拦截把字节码做修改。
  2. 获取所有已经被加载过的类。
  3. 获取某个对象的大小。
  4. 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载。
  5. 将某个jar加入到classpath里供AppClassload去加载。
  6. 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配。

agent案例一

agentdemo1项目

普通maven工程。该项目主要编写premain方法。

package com.david.agent; 

import java.lang.instrument.Instrumentation; 

public class PremainTest1 { 
    public static void premain(String agentArgs, Instrumentation inst) { 
        System.out.println("hello agent demo1"); 
        System.out.println("agentArgs =====> " + agentArgs); 
    }
}

MANIFREST.MF手工编写
不推荐使用这种方式。比较麻烦,简单了解一下即可。 在 resources 目录下新建目录:META-INF,在该目录下新建文件MANIFREST.MF 。文件内容如下:

Manifest-Version: 1.0 
Premain-Class: com.david.agent.PremainTest1 
Agent-Class: com.david.agent.PremainTest1 
Can-Redefine-Classes: true 
Can-Retransform-Classes: true 
Build-Jdk-Spec: 1.8 
Created-By: Maven Jar Plugin 3.2.0

这里如果你不去手动指定的话,直接打包,默认会在打包的文件中生成一个MANIFREST.MF文件 。 默认的文件中包含当前的一些版本信息,当前工程的启动类。我们需要增加参数做更多的事情。
Premain-Class :包含 premain 方法的类(类的全路径名)
Agent-Class :包含 agentmain 方法的类(类的全路径名)
Boot-Class-Path :设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为false(可选)
MANIFREST.MF 插件一
介绍几种主流的可执行jar包打包插件。
使用maven-jar-plugin插件进行打包。

<build> 
    <finalName>agentdemo1</finalName> 
    <plugins> 
        <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-jar-plugin</artifactId> 
            <version>3.2.0</version> 
            <configuration> 
                <archive> 
                    <!--自动添加META-INF/MANIFEST.MF --> 
                    <manifest> 
                        <addClasspath>true</addClasspath> 
                    </manifest> 
                    <manifestEntries> 
                        <!--permain方法所在类的完全限定名--> 
                        <Premain- Class>com.david.agent.PremainTest1</Premain-Class> 
                        <Agent- Class>com.david.agent.PremainTest1</Agent-Class> 
                        <Can-Redefine-Classes>true</Can-Redefine- Classes> 
                        <Can-Retransform-Classes>true</Can-Retransform- Classes> 
                    </manifestEntries> 
                </archive> 
            </configuration> 
        </plugin> 
    </plugins> 
</build>

MANIFREST.MF插件二
使用maven-assembly-plugin插件进行打包。

<plugin> 
    <groupId>org.apache.maven.plugins</groupId> 
    <artifactId>maven-assembly-plugin</artifactId> 
    <configuration> 
        <appendAssemblyId>false</appendAssemblyId> 
        <descriptorRefs> 
            <descriptorRef>jar-with- dependencies</descriptorRef> 
        </descriptorRefs> 
        <archive> 
            <!--自动添加META-INF/MANIFEST.MF --> 
            <manifest> 
                <addClasspath>true</addClasspath> 
            </manifest> 
            <manifestEntries> 
                <Premain- Class>com.lagou.agent.PremainTest1</Premain-Class> 
                <Agent- Class>com.lagou.agent.PremainTest1</Agent-Class> 
                <Can-Redefine-Classes>true</Can-Redefine- Classes> 
                <Can-Retransform-Classes>true</Can-Retransform- Classes> 
            </manifestEntries> 
        </archive>
    </configuration> 
    <!-- 其中<phase>package</phase>、<goal>single</goal> 即表示在执行package打包时,执行assembly:single --> 
    <executions> 
        <execution> 
            <id>make-assembly</id> 
            <phase>package</phase> 
            <goals> 
                <goal>single</goal> 
            </goals> 
        </execution> 
    </executions> 
</plugin>

注意事项
选择插件二方式打包时。如果java.exe进程被占用,使用mvn clean package命令或报错。可以直接 在idea插件中assembly中执行assembly:single。或者执行mvn package assembly:single命令。
MANIFREST.MF 插件三
使用maven-shade-plugin插件进行打包。

<plugin> 
    <groupId>org.apache.maven.plugins</groupId> 
    <artifactId>maven-shade-plugin</artifactId> 
    <version>3.2.4</version> 
    <executions> 
        <execution> 
            <phase>package</phase> 
            <goals> 
                <goal>shade</goal> 
            </goals> 
            <configuration> 
                <transformers> 
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTr ansformer"> 
                        <manifestEntries> 
                            <Premain- Class>com.lagou.agent.PremainTest1</Premain-Class> 
                        </manifestEntries> 
                    </transformer> 
                </transformers> 
            </configuration> 
        </execution> 
    </executions> 
</plugin>

打包命令

mvn clean package

编译工程
选择插件一方式。将agentdemo1项目打包。

mvn clean package

agenttest1项目

普通maven工程。该项目主要编写main方法。
编写main方法

package com.lagou.test1; 

public class AgentTest1 { 
    public static void main(String[] args) { 
        System.out.println("这里是agent第一个main方法测试。"); 
    } 
}

测试main方法
测试main方法是否正常运行。

idea配置

必须先运行一次main,然后点击编辑AgentTest1启动类->edit configurations -> VM options
测试agent一
不带参数测试

-javaagent:D:\ideaworkspaces\202001test\agentdemo1\target\agentdemo1.jar 
运行main方法测试:

测试agent二
带参数测试

-javaagent:D:\ideaworkspaces\202001test\agentdemo1\target\agentdemo1.jar=lagou 
运行main方法测试:

permain方法

重点就在 premain 方法,也就是我们今天的标题。从字面上理解,就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行 -javaagent 所指定 jar 包内 Premain-Class 这个类的 premain 方法,其中,该方法可以签名如下:
1.public static void premain(String agentArgs, Instrumentation inst)
2.public static void premain(String agentArgs)
JVM 会优先加载 1 签名的方法,加载成功忽略 2,如果1 没有,加载 2 方法。
这个逻辑在sun.instrument.InstrumentationImpl 类中:
InstrumentationImpl 类源码loadClassAndStartAgent方法的217行左右。

private void loadClassAndStartAgent(String var1, String var2, String var3) throws Throwable {
    ClassLoader var4 = ClassLoader.getSystemClassLoader(); 
    Class var5 = var4.loadClass(var1); 
    Method var6 = null; 
    NoSuchMethodException var7 = null; 
    boolean var8 = false; 

    try {
        var6 = var5.getDeclaredMethod(var2, String.class, Instrumentation.class); 
        var8 = true; 
    } catch (NoSuchMethodException var13) { 
        var7 = var13; 
    }
    
    if (var6 == null) { 
        try {
            var6 = var5.getDeclaredMethod(var2, String.class); 
        } catch (NoSuchMethodException var12) { 
        } 
    }
}

package com.lagou.agent; 

import java.lang.instrument.Instrumentation; 

public class PremainTest1 { 
    /* public static void premain(String agentArgs, Instrumentation inst) { 
        System.out.println("hello agent demo1"); 
        System.out.println("agentArgs =====> " + agentArgs); 
    }*/
    
    public static void premain(String agentArgs) { 
        System.out.println("hello agent demo2"); 
        System.out.println("agentArgs =====> " + agentArgs); 
    } 
}

agent案例二

agentdemo1项目

package com.lagou.agent; 

import java.lang.instrument.ClassFileTransformer; 
import java.lang.instrument.IllegalClassFormatException; 
import java.lang.instrument.Instrumentation; 
import java.security.ProtectionDomain; 

public class PremainTest1 {
    public static void premain(String agentArgs, Instrumentation inst) { 
        System.out.println("hello agent demo1"); 
        System.out.println("agentArgs =====> " + agentArgs); 
        inst.addTransformer(new ClassFileTransformer() { 
            @Override 
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 
                System.out.println("premain load Class :" + className); return classfileBuffer; 
                } 
        }, true);       
    }
    public static void premain(String agentArgs) { 
        System.out.println("hello agent demo2"); 
        System.out.println("agentArgs =====> " + agentArgs); 
    } 
}

JVMTI(Java Virtual Machine Tool Interface)是一套本地编程接口集合,它提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。关于 JVMTI 的详细信息,请参考 Java SE 6 文档当中的介绍。
java.lang.instrument 包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。
Instrumentation 的最大作用,就是类定义动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 – javaagent 参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。
和类加载器比较
类加载器也可以实现运行时修改代码。但是对代码的侵入性很高。使用 java agent 能让修改字节码这个动作化于无形,对业务透明,减少侵入性。
agent的缺点
需要设置参数javaagent

IDEA 保姆级安装教程:  http://note.youdao.com/s/Wq2GSETJ  

计算机专业常用毕业设计集合:  http://note.youdao.com/s/PIJHOqnk  

Logo

更多推荐