skywalking-介绍

一、skywalking是什么

skywalking是一个可观测性分析平台和应用性能管理系统,它也是基于OpenTracing规范、开源的AMP系统。Skywalking提供分布式跟踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。支持Java

二、SkyWalking 分为三个核心部分:

1、Agent(探针):Agent 运行在各个服务实例中,负责采集服务实例的 Trace 、Metrics 等数据,然后通过 gRPC方式上报给SkyWalking后端。

2、OAP:SkyWalking 的后端服务,其主要责任有两个。
一个是负责接收 Agent 上报上来的 Trace、Metrics 等数据,交给 Analysis Core (涉及SkyWalkingOAP 中的多个模块)进行流式分析,最终将分析得到的结果写入持久化存储中。SkyWalking 可以使用ElasticSearch、H2、MySQL等作为其持久化存储,一般线上使用ElasticSearch 集群作为其后端存储。
另一个是负责响应 SkyWalking UI 界面发送来的查询请求,将前面持久化的数据查询出来,组成正确的响应结果返回给 UI界面进行展示。

3、UI 界面:SkyWalking 前后端进行分离,该 UI 界面负责将用户的查询操作封装为 GraphQL 请求提交给 OAP后端触发后续的查询操作,待拿到查询结果之后会在前端负责展示。

三、教程

安装agent

skywalking-介绍_第1张图片

四、Skywalking的几大特点:

  1. 多语言自动探针,Java,.NET Core和Node.JS。
  2. 多种监控手段,语言探针和service mesh。
  3. 轻量高效。不需要额外搭建大数据平台。
  4. 模块化架构。UI、存储、集群管理多种机制可选。
  5. 支持告警。
  6. 优秀的可视化效果。

五、Open Tracing

OpenTracing中最核心的概念就是Trace

1、Trace
在广义上,一个trace代表了一个事务或者流程在(分布式)系统中的执行过程。在OpenTracing标准中,trace是多个span组成的一个有向无环图(DAG),每一个span代表trace中被命名并计时的连续性的执行片段。Trace表示一个调用链路,由全局唯一的TraceID标识。Trace由Span组成。

2、 Span
(一般)表示一个函数调用,由全局唯一的SpanID标识。Span组成的Trace实际上是一颗树结构,除了根Span外,每个Span都会有一个父Span。

Span里面的信息包括:操作的名字,开始时间和结束时间,可以附带多个 key:value 构成的 Tags(key
必须是String,value可以是 String, bool 或者数字),还可以附带 Logs 信息(不一定所有的实现都支持)
也是 key:value形式。

3、Metric和Tracing的区别
Metrics和Tracing属于开箱即用的一套API,其目的是为了监控、跟踪程序调用。下面看看二者的区别

Metric主要用来进行数据的统计,比如HTTP请求数的计算。 Metrics即度量指标。其原理是,将要监测的值记录在特定的全局变量中,然后通过HTTP的形式向外提供查询这些指标的接口,即Exporter。Prometheus会通过Exporter拉取这些数据,并存放在时序数据库中,可通过PromQL进行查询。

2、Tracing即链路跟踪。其目的是在分布式系统中,完成一个用户请求可能需要多个子系统之间的相互调用,而子系统为了实现高可用都会有多个节点,导致排查问题非常困难。Tracing相对于Metrics来说,实现会更复杂

六、启动带有agent相关的服务

通过注入agent的方式启动skywalking_mysql服务,注意skywalking_mysql要打成jar包

java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_mysql/skywalking-agent.jar -jar skywalking_mysql.jar

让Skywalking以指定appname的方式启动skywalking_mysql,最终的结果就是我们在skywalking后台可以看到多了一个skywalking_mysql服务

java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent_mysql/skywalking-agent.jar -Dskywalking.agent.service_name=skywalking_mysql -jar skywalking_mysql.jar &

七、项目中获取traceId


<dependency>
	<groupId>org.apache.skywalkinggroupId>
	<artifactId>apm-toolkit-traceartifactId>
	<version>${skywalking.version}version>
dependency>

引入这个就可以在系统中通过TraceContext.traceId() 来获取traceId,如果不引,只是在系统不能通过代码去获取,但是在skywalking也是可以采集到的。

七、排除不需要监控的端点

有的端点不需要监控,比如Swagger相关的端点,要把他排除掉:

java -javaagent:/usr/local/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar 
-Dskywalking.agent.service_name=skywalking_plugins 
-Dskywalking.trace.ignore_path=/exclude jar skywalking_plugins.jar &

通过命令启动服务,Dskywalking.trace.ignore_path标识不监控的url

八、告警功能

Skywalking每隔一段时间根据收集到的链路追踪的数据和配置的告警规则(如服务响应时间、服务响应时间百分比)等,判断如果达到阈值则发送相应的告警信息。发送告警信息是通过调用webhook接口完成,具体的webhook接口可以使用者自行定义,从而开发者可以在指定的webhook接口中编写各种告警方式,比如邮件、短信等。告警的信息也可以在RocketBot中查看到。

默认的告警规则配置,位于skywalking安装目录下的config文件夹下 alarm-settings.yml 文件中:

https://img-blog.csdnimg.cn/2db751d4c4de46f9b070d69ad12ecff9.png

比如定义:

  1. 最近3分钟内服务的平均响应时间超过1秒
  2. 最近2分钟服务成功率低于80%
  3. 最近3分钟90%服务响应时间超过1秒
  4. 最近2分钟内服务实例的平均响应时间超过1秒
@RestController
public class WebHooks {
    private List<AlarmMessage> lastList = new ArrayList<>();
    
    //产生告警时调用的接口
    @PostMapping("/webhook")
    public void webhook(@RequestBody List<AlarmMessage> alarmMessageList){
        lastList = alarmMessageList;
    }

    //为了方便,新增一个接口展示告警的信息接口
    @GetMapping("/show")
    public List<AlarmMessage> show(){
        return lastList;
    }
}

注意一定要书写一个url=/webhook的接口

九、搭建自己的java agent工程

public class PreMainAgent {
    /**
     * 在这个 premain 函数中,开发者可以进行对类的各种操作。
     * 1、agentArgs 是 premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main
     函数不同的是,
     * 这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
     * 2、Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。*
     * java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这
     个包的核心部分,
     * 集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
     类中提供两个静态方法,方法名均为premain,不能拼错。
     在pom文件中添加打包插件:
     * @param agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("=========premain方法执行1========");
        System.out.println(agentArgs);
    }
    /**
     * 如果不存在 premain(String agentArgs, Instrumentation inst)
     * 则会执行 premain(String agentArgs)
     * @param agentArgs
     */
    public static void premain(String agentArgs) {
        System.out.println("=========premain方法执行2========");
        System.out.println(agentArgs);
    }
}

类中提供两个静态方法,方法名均为premain,不能拼错。

<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-pluginartifactId>
            <configuration>
                <appendAssemblyId>falseappendAssemblyId>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependenciesdescriptorRef>
                descriptorRefs>
                <archive>
                    
                    <manifest>
                        <addClasspath>trueaddClasspath>
                    manifest>
                    <manifestEntries>
                        <Premain-Class>PreMainAgentPremain-Class>
                        <Agent-Class>PreMainAgentAgent-Class>
                        <Can-Redefine-Classes>trueCan-Redefine-Classes>
                        <Can-Retransform-Classes>trueCan-RetransformClasses>
                    manifestEntries>
                archive>
            configuration>
            <executions>
                <execution>
                    <id>make-assemblyid>
                    <phase>packagephase>
                    <goals>
                        <goal>singlegoal>
                    goals>
                execution>
            executions>
        plugin>
    plugins>
build>

再把项目打包package,这样就得到一个agent的jar

在IDEA的VM options中添加代码

-javaagent:路径\java-agent-demo-1.0-SNAPSHOT.jar=HELLOAGENT

十、ByteBuddy

Byte Buddy致力于解决字节码操作和instrumentation API的复杂性。Byte Buddy提供了额外的API来生成Java agent,可以轻松的增强我们已有的代码。注意是增强代码,比如我们要统计方法调用时间,他可以通过类似于切面一样的东西增强我们的目标代码

在我们自己的agent项目中添加依赖

<dependencies>
    <dependency>
        <groupId>net.bytebuddygroupId>
        <artifactId>byte-buddyartifactId>
        <version>1.9.2version>
    dependency>
    <dependency>
        <groupId>net.bytebuddygroupId>
        <artifactId>byte-buddy-agentartifactId>
        <version>1.9.2version>
    dependency>
dependencies>
public class PreMainAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        //创建一个转换器,转换器可以修改类的实现
        //ByteBuddy对java agent提供了转换器的实现,直接使用即可
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, 
                                                    TypeDescription typeDescription, 
                                                    ClassLoader classLoader, 
                                                    JavaModule javaModule) {
                return builder
                        .method(ElementMatchers.<MethodDescription>any()) // 拦截任意方法
                        .intercept(MethodDelegation.to(MyInterceptor.class));//拦截到的方法委托给TimeInterceptor
            }
        };

        //Byte Buddy专门有个AgentBuilder来处理Java Agent的场景
        new AgentBuilder.Default()
                .type(ElementMatchers.nameStartsWith("com.agent"))// 根据包名前缀拦截类
                .transform(transformer)// 拦截到的类由transformer处理
                .installOn(inst);
    }
}

先生成一个转换器,ByteBuddy提供了java agent专用的转换器。转换器的条件如下
1、实现Transformer接口利用builder对象来创建一个转换器。转换器可以配置拦截方法的格式,比如用名称,本例中拦截所有方法
2、定义一个拦截器类MyInterceptor。创建完拦截器之后可以通过Byte Buddy的AgentBuilder建造者来构建一个agent对象。AgentBuilder可以对指定的包名前缀来生效,同时需要指定转换器对象。

public class MyInterceptor {
    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable)throws Exception {
        long start = System.currentTimeMillis();
        try {
            //执行原方法
            return callable.call();
        } finally {
            //打印调用时长
            System.out.println(method.getName() + ":" + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

MyInterceptor就是一个拦截器的实现,统计的调用的时长。参数中的method是反射出的方法对象,而callable就是调用对象,可以通过callable.call()方法来执行原方法。

链路追踪对比
https://blog.csdn.net/A123638/article/details/123117142
https://blog.csdn.net/z45351/article/details/125779241

日志采集:
https://blog.csdn.net/xiaoweite1/article/details/121865241
https://blog.csdn.net/ruocheng6/article/details/124415664

你可能感兴趣的:(链路追踪,skywalking)