标签 性能监控 下的文章

作者:肖振威

背景

随着云端业务规模的持续扩大,AI 训练数据、实时日志与多媒体资料等数据量呈现指数级增长,云存储因此逐渐成为主流选择,同时也带来了 I/O 请求量的快速上升。在共享式的多租户架构中,多个租户共同使用底层存储资源,高并发访问极易引发 I/O 资源争抢与性能瓶颈。此外,混合云与多云部署日益普及,数据在多个云环境之间频繁流动,而不同云服务商在存储策略与监控机制上的不一致,使得 I/O 类故障的定位与追溯变得更加复杂。为提升此类问题的处理效率,阿里云云监控 2.0 结合 SysOM 智能诊断功能围绕常见的 I/O 异常场景,构建了一套覆盖“异常检测—根因分析—修复建议”全链路的 I/O 一键诊断功能。

业务痛点解析

痛点一:用户难以准确判断 IO 异常类型

大多数用户对 IO 问题的具体类型缺乏清晰认知,例如往往搞不清当前是 IO 延迟升高、IO 吞吐被打满,还是其它类型的异常,导致很难主动选用对应的排障工具和方法,只能依靠运维专家介入排查,整体诊断效率偏低,人力投入也随之增加。IO 一键诊断聚焦 IO 延时偏高、流量异常、iowait 居高不下等高频场景,自动捕捉 IO 子系统的异常特征,帮助用户快速完成问题类型的判定。

痛点二:异常发生瞬间难以“抓现场”,取证不充分

传统监控系统通常只采集操作系统层面的通用 IO 指标,比如 await、util、tps、bps 等,并以指标突变作为告警条件。然而,当指标被检测到异常时,真实问题往往已经发生甚至结束,此时再想获取更细致的采样和上下文信息,往往为时已晚,关键线索已经流失,难以形成完整的诊断证据链。要做到有效定位,就必须尽可能在异常刚出现或仍在持续时就触发针对性采集,因此,快速识别并及时行动,是获取最佳诊断数据的关键。

痛点三:指标体系割裂,监控数据与诊断结论之间缺乏直连

现有监控往往仅提供一组相互独立的指标,彼此缺乏联动,也没有与具体 IO 故障类型建立直观映射。以 util(磁盘繁忙度)偏高为例,实际分析时还需参考 await 等多项指标,并结合设备的理论 iops、bps 上限进行综合判断。即便勉强推断出问题类型,接下来仍离不开对各种诊断工具的经验性操作,包括如何按照指标数值选择合适的采样区间、参数配置等。IO 一键诊断的设计目标,就是将这一串复杂的关联分析与工具选型过程封装在系统内部,对用户直接呈现整理好的诊断报告和结论。

解决方案

架构介绍

在阿里云云监控 2.0 中,SysOM 管控模块原本就支持对 IO 延迟异常、IO 量异常以及 iowait 高等问题开展诊断。不过,大部分客户并不希望在业务环境上长时间运行高频诊断程序,以免对生产带来干扰。因此,IO 一键诊断采用了“监控先行、按需抓取”的架构:在用户指定的诊断时间段内,系统定期读取 IO 监控指标,用于异常识别与问题圈定,一旦满足条件,再触发具体的子诊断工具进行深度分析并输出报告,构成一个从发现到定位的闭环流程。

考虑到不同业务类型对 IO 行为和性能阈值的容忍度不尽相同,如果强行规定统一的固定阈值,势必会导致误报大量增加或严重漏报。因此,IO 一键诊断引入“动态阈值”机制进行异常识别,其总体处理链路可以概括为:

image

  • 指标采集: 定期从系统中抓取关键 IO 指标,如 await、util、tps、iops、qu-size、iowait 等。
  • 异常检测: 当采集到的指标突破动态阈值,就将其标记为潜在异常。动态阈值的计算方法是整个检测环节的核心,后文会展开说明。
  • 自动诊断触发: 依据异常的指标类型与特征,自动选择合适的诊断工具,并设置触发频率限制,避免频繁调用。
  • 结果处理与展示: 对诊断输出进行归纳和可视化呈现,为用户提供导致问题的根本原因以及可执行的优化建议。

实现原理

指标采集机制

当用户在控制台启动 IO 一键诊断后,系统会按配置好的时间间隔(cycle 毫秒)循环读取 iowait、iops、bps、qusize、await、util 等一系列 IO 指标,并在每个周期对最新采集的数据做异常检测判断。

动态阈值计算

为了能在秒级甚至更细粒度下捕获 IO 突发、短时抖动等异常,必须将各类单一 IO 指标联动起来,从整体上刻画 IO 子系统的“正常波动区间”。动态阈值就是用来界定这一“正常区间”和“异常尖峰”的边界。其计算过程主要分为三层:基础阈值、补偿阈值和最小静态阈值。

基础阈值:刻画整体波动幅度

从时间序列的角度看,IO 指标在大多数时刻处于平稳运行状态,曲线起伏较小;当出现异常负载或者突发流量时,曲线会突然出现明显偏离均值的峰值。因此,首要任务是利用基础阈值,找出这些显著高于日常波动的“尖峰”。

实现策略是:使用一个滑动时间窗口持续观察数据点,在每个窗口中计算所有点相对于窗口平均值的“最大偏离量”,把这个偏离量记为该窗口的“瞬时波动值”;随后对连续多个窗口的“瞬时波动值”求平均,形成动态更新的“基础阈值”。随着新数据不断进入,该阈值也会自适应地调整,始终反映 IO 指标近期的真实波动特征。

image

补偿阈值:削弱基础阈值快速下降带来的误报

基础阈值曲线(如示意图中的黄色线条)虽然能够反映指标的总体波动情况,但在系统处于稳定期时,IO 指标通常只在很窄的一段区间内轻微波动,此时基础阈值可能随波动减弱而快速下降,容易让一些微小的正常抖动被误判为异常。因此,需要额外引入一个“补偿阈值”,叠加在基础阈值之上,对其下降速度进行一定缓冲,从而抑制误报。

image

具体逻辑是:当系统监测到基础阈值在一段时间内持续走低,可以认为当前进入了相对“安静”的常态阶段。此时先过滤明显噪声点,再在剩余的稳定数据里计算一个“常稳态补偿值”,以刻画这类稳定状态下的细小波动。补偿值尚未收敛前,先用当前窗口内出现过的最大基础阈值暂时代替,并在每个新窗口开始时重新计算。一旦基础阈值停止下降或开始回升,就意味着系统波动模式发生了变化,此时补偿机制会被重置,重新进入更宏观的观察期。

image

最小阈值:兜底的静态门槛

最小静态阈值可以理解为预先设定的“绝对下限”,是业务方能接受的最低告警基线。最终用于判定异常的阈值,是“最小静态阈值”和“动态调整阈值(基础阈值 + 补偿值)”之间的较大者。只有当指标既超过了日常波动的正常范围,又突破了业务底线时,才真正被视为异常事件。

此外,如果指标本身已经明显高于“最小静态阈值”,则无需再额外叠加常态补偿值,此时仅以基础阈值作为判断依据即可,将分析重点聚焦在更显著的异常波动上。

image

异常识别策略

在运行时,一旦采集到的某项 IO 指标值高于其对应的动态阈值,即可认为存在异常风险。虽然不同指标(如 iowait、util、iops 等)的判定逻辑略有差异,但整体遵从以下共通规则:

  • 确定告警基线: 为每一类指标定义一条“警戒线”,其数值为“最小静态阈值”和“动态阈值”中的最大值,既考虑业务底线,也考虑历史波动范围。
  • 决定是否触发诊断: 当监控值超过警戒线,同时满足一定的监测条件(如持续时间、触发次数等),就可以启动对应的诊断流程。
  • 持续更新模型: 随着新数据不断加入,动态阈值会被持续修正,使其适配当前环境的正常波动模式,而非依赖一次性的静态配置。

智能诊断与频率控制

当系统确认存在 IO 异常后,一键诊断模块会自动调用相应的分析工具,抓取关键现场信息并进行自动化处理,帮助用户快速锁定问题。为避免过于频繁的诊断操作影响业务,系统通过以下两个参数对诊断频率进行约束:

  • 诊断冷静期(triggerInterval): 规定两次诊断之间必须间隔的最短时间,用来避免在短时间内重复对同一类异常进行频繁扫描。
  • 异常累积阈值(reportInterval): 设置触发诊断所需的异常累积条件。当该值为 0 时,只要异常满足冷静期结束的条件,就立即启动诊断;当该值为非 0 时,则需要在冷静期之后、限定时间窗口内出现一定次数的异常事件,才会真正触发。

根因分析

在完成现场数据采集之后,面对复杂多样的系统信息,如何从中筛选出与当前问题强相关的线索,是传统人工分析的难点。IO 一键诊断在工具层面内置了一套自动分析逻辑,能从采集结果中提炼结论,并以结构化信息的形式反馈给用户,包括但不限于:

  • IO Burst 场景: 分析在异常时间段内各进程对 IO 的贡献度,在报告中标明最“耗 IO”的进程。对于写 buffer IO 而由内核 kworker 线程负责刷脏的情况,也能追溯到最初发起写入的用户进程。
  • IO 延迟异常: 统计并展示异常区间内 IO 延迟的整体分布情况,标记延迟最高的路径(如对应的设备或文件/目录),帮助快速找到性能瓶颈所在。
  • iowait 异常偏高: 记录和展示导致 iowait 偏高的关键进程,以及引发大量等待的具体原因(例如磁盘被占满、脏页刷写过慢等)。

案例分析

iowait 高

在某些场景下,业务反馈系统整体响应慢,通过监控发现 iowait 指标异常升高。借助 IO 一键诊断,可以直接定位到哪一个或哪些进程在大量等待磁盘 IO,以及每个进程累计等待的时间长度,并进一步分析等待背后的原因。

在示例案例中,诊断结果显示:业务写入量过大导致 IO 压力偏高,系统中脏页堆积,最终使业务进程 task_server 长时间阻塞在 IO 等待上。针对这种情况,报告建议谨慎下调 dirty_ratio、dirty_bytes 等内核参数,以减少一次性刷脏量,降低磁盘压力,从而缓解 iowait 过高问题。

image

IO延迟高

另一类常见问题是写 IO 的延迟持续走高。某用户通过基础监控发现写入延迟异常后,通过 IO 一键诊断进行进一步排查。

image

诊断报告指出,在问题发生期间,DiskBlockWrite 进程是主要的 IO 负载来源,并且耗时主要集中在刷脏阶段,也就是说核心瓶颈在于磁盘将缓存数据落盘的过程。依据这一结论,系统给出两类优化建议:一是调整业务逻辑,减少短时间内大量 buffer IO 的写入;二是通过适当调整 dirty_ratio、dirty_background_ratio 等参数,控制脏页生成和回写的节奏,从系统层面降低写 IO 延迟。

image

相关链接:

[1] IO 一键诊断

https://help.aliyun.com/zh/cms/cloudmonitor-2-0/io-key-diagnosis

[2] 云监控-ECS 洞察-SysOM 系统诊断

https://cmsnext.console.aliyun.com/next/region/cn-shanghai/wo...

[3] 操作系统控制台实例纳管

https://help.aliyun.com/zh/alinux/user-guide/system-management

Java Agent 是 Java 提供的一种在 JVM 启动时或运行时动态修改字节码的强大机制,广泛应用于 APM 监控(如 SkyWalking、Pinpoint)、热部署(如 JRebel)、代码覆盖率(JaCoCo)、故障注入、安全审计等场景。一、Java Agent 的两种模式模式加载时机典型用途Premain AgentJVM 启动时(-javaagent)APM 探针、性能监控、字节码增强Attach AgentJVM 运行时动态 attach线上诊断(如 Arthas)、动态开关二、核心原理:InstrumentationAgent 通过 java.lang.instrument.Instrumentation 接口实现:retransformClasses():重新转换已加载的类(需类支持 retransformation)redefineClasses():直接替换类的字节码(限制多,不常用)addTransformer():注册 ClassFileTransformer,在类加载时修改字节码三、快速入门:编写一个简单 Agent步骤 1:创建 Agent 入口类package com.example;

import com.example.transformer.RestTemplateTraceAdvice;
import com.example.transformer.TraceAdvice;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;

import java.lang.instrument.Instrumentation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

public class TraceAgent {

private final static String appId ;

private static final Set<String> REQUEST_MAPPING_ANNOTATIONS = new HashSet<>(Arrays.asList(
        "org.springframework.web.bind.annotation.RequestMapping",
        "org.springframework.web.bind.annotation.GetMapping",
        "org.springframework.web.bind.annotation.PostMapping",
        "org.springframework.web.bind.annotation.PutMapping",
        "org.springframework.web.bind.annotation.DeleteMapping"
));

private static CustomAgentListener customListener;

static {
    appId = System.getProperty("appId");
}

public static void premain(String agentArgs, Instrumentation inst) {
    install(inst);
}

public static void agentmain(String agentArgs, Instrumentation inst) {
    install(inst);
}

private static void install(Instrumentation inst) {
    // 创建自定义监听器,输出到指定文件,只记录指定包的类
    customListener = new CustomAgentListener(
            "/Users/dsy/code/agent-demo/logs/"+appId+"-bytebuddy-agent.log",  // 日志文件路径
            "com.example"               // 只记录 com.example 包下的类
    );

    new AgentBuilder.Default()
            .with(customListener) // 👈 关键:输出匹配详情
            .disableClassFormatChanges()
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
            .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
            .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY) // 👈 启用完整类型解析
            .type(
                    isAnnotatedWith(named("org.springframework.stereotype.Controller"))
                            .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.RestController")))
            )
            .transform((builder, typeDescription, classLoader, module) ->
                    builder.visit(Advice.to(TraceAdvice.class)
                            .on(anyMethodAnnotatedWithRequestMapping()))

// builder.method(any()).intercept(MethodDelegation.to(NoOpInterceptor.class))

            )
            .type(named("org.springframework.web.client.RestTemplate"))
            .transform((builder, td, cl, module) ->
                    builder.visit(Advice.to(RestTemplateTraceAdvice.class)
                                    .on(named("exchange")
                                            .and(takesArguments(4))
                                            .or(takesArguments(5))
                                            .or(takesArguments(6))))
            )
            .installOn(inst);

    System.out.println("[Agent] Controller tracing agent installed.");
}

private static ElementMatcher.Junction<MethodDescription> anyMethodAnnotatedWithRequestMapping() {
    return isAnnotatedWith(named("org.springframework.web.bind.annotation.RequestMapping"))
            .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.GetMapping")))
            .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PostMapping")))
            .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PutMapping")))
            .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.DeleteMapping")));
}

// 添加关闭方法,用于清理资源
public static void shutdown() {
    if (customListener != null) {
        customListener.close();
    }
}

}步骤 2:实现 TraceAdvicepackage com.example.transformer;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.bytebuddy.asm.Advice;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.UUID;

import static com.example.transformer.TraceContextHolder.PARENT_APP_ID;
import static com.example.transformer.TraceContextHolder.X_TRACE_ID;

public class TraceAdvice {

public final static String appId ;

public final static ObjectMapper objectMapper;

static {
    appId = System.getProperty("appId");
    objectMapper = new ObjectMapper();
}

@Advice.OnMethodEnter
public static void enter(@Advice.AllArguments Object[] args) {

    TraceContextHolder.TraceContext traceContext = TraceContextHolder.traceContext();

    // 尝试从参数中提取 HttpServletRequest
    HttpServletRequest request = null;
    for (Object arg : args) {
        if (arg instanceof HttpServletRequest) {
            request = (HttpServletRequest) arg;
            break;
        }
    }

    String traceId = null;
    String parentAppId = null;
    if (request != null) {
        // 优先从 Header 中获取 traceId(例如:X-Trace-Id)
        traceId = request.getHeader(X_TRACE_ID);
        parentAppId = request.getHeader(PARENT_APP_ID);
    }

    if (traceId == null || traceId.trim().isEmpty()) {
        // 未传入,则生成新 traceId(建议用 UUID 或 Snowflake)
        traceId = "trace-" + UUID.randomUUID().toString().replace("-", "").substring(0, 32);
    }

    if (parentAppId == null || parentAppId.trim().isEmpty()){
        parentAppId = "0" ;
    }

    traceContext.setTraceId(traceId);
    traceContext.setArgs(args.toString());
    traceContext.setAppId(appId);
    traceContext.setParentAppId(parentAppId);
    traceContext.setTraceSpanStartTime(System.currentTimeMillis());

    // 绑定到当前线程

// TraceContextHolder.setTraceContext(traceContext);

    System.err.println(">>> Entering method with args: " + Arrays.toString(args));
}

@Advice.OnMethodExit
public static void exit(@Advice.Return Object result) {
    TraceContextHolder.TraceContext traceContext = TraceContextHolder.traceContext();
    traceContext.setTraceSpanEndTime(System.currentTimeMillis());
    try {
        traceContext.setResult(objectMapper.writeValueAsString(result));
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }
    System.err.println("<<< Exiting method, returned: " + traceContext.toSting());
}

}步骤 3:用于跟踪调用链的上下文在 src/main/resources/META-INF/MANIFEST.MF 中声明:// com/example/transformer/TraceContextHolder.java
package com.example.transformer;

public class TraceContextHolder {

public final static String X_TRACE_ID = "X-Trace-Id";
public final static String PARENT_APP_ID = "X-Parent-APP-Id";

static String FORMAT = "traceId:%s,parentAppId:%s,appId:%s,traceSpanStartTime:%d,traceSpanEndTime:%d,args:%s,result:%s";
private static final ThreadLocal<TraceContext> TRACE = new ThreadLocal<>();

public static void setTraceContext(TraceContext traceContext) {
    TRACE.set(traceContext);
}

public static void clear() {
    TRACE.remove();
}

public static TraceContext traceContext() {
    TraceContext object;
    if (TRACE.get() != null) {
        object = TRACE.get();
    } else {
        object = new TraceContext();
        TRACE.set(object);
    }
    return object;
}

public static class TraceContext{
    private String traceId;
    private String parentAppId;
    private String appId;
    private Long traceSpanStartTime;
    private Long traceSpanEndTime;
    private String args;

    private String result;

    public String getTraceId() {
        return traceId;
    }

    public void setTraceId(String traceId) {
        this.traceId = traceId;
    }

    public String getParentAppId() {
        return parentAppId;
    }

    public void setParentAppId(String parentAppId) {
        this.parentAppId = parentAppId;
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public Long getTraceSpanStartTime() {
        return traceSpanStartTime;
    }

    public void setTraceSpanStartTime(Long traceSpanStartTime) {
        this.traceSpanStartTime = traceSpanStartTime;
    }

    public Long getTraceSpanEndTime() {
        return traceSpanEndTime;
    }

    public void setTraceSpanEndTime(Long traceSpanEndTime) {
        this.traceSpanEndTime = traceSpanEndTime;
    }

    public String getArgs() {
        return args;
    }

    public void setArgs(String args) {
        this.args = args;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public String toSting(){

        return String.format(FORMAT,traceId,parentAppId,appId,traceSpanStartTime,traceSpanEndTime,args,result);

    }
}

}步骤 4:打包 & 使用如果用 Maven,可通过 maven-jar-plugin 自动生成:<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.example</groupId>
    <artifactId>agent-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

<artifactId>agent3</artifactId>

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <!-- ByteBuddy 核心 -->
    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy</artifactId>
        <version>1.12.10</version>
    </dependency>
    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy-agent</artifactId>
        <version>1.12.10</version>
    </dependency>

    <!-- Spring Web(仅用于类型判断,非强制) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.3.31</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.0</version> <!-- 使用最新稳定版 -->
    </dependency>
</dependencies>
<build>
    <plugins>
        <!-- 使用 shade plugin 打包 fat jar -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.5.0</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <manifestEntries>
                                    <Premain-Class>com.example.TraceAgent2</Premain-Class>
                                    <Agent-Class>com.example.TraceAgent2</Agent-Class>
                                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                </manifestEntries>
                            </transformer>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

</project>四、创建两个web应用验证trace到调用生命周期
图片
1、web-apppackage com.example.demo.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class BeanConfig {

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

}
package com.example.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class HelloController {

private static final Logger logger = LoggerFactory.getLogger(HelloController.class);

@Resource
private RestTemplate restTemplate;

@GetMapping("/hello")
public String hello(@RequestParam(defaultValue = "World") String name) {
    logger.info("Processing hello request for: {}", name);
    String url = "http://localhost:8081/shopping?commodity=香蕉";
    String r = restTemplate.exchange(url, HttpMethod.GET,null,String.class).getBody();
    return "Hello, " + name + "!" + " commodity = " + r;
}

@PostMapping("/user")
public String createUser(@RequestBody String userData) {
    logger.info("Creating user with data: {}", userData);
    return "User created: " + userData;
}

@GetMapping("/error")
public String error() {
    logger.info("Triggering error");
    throw new RuntimeException("Test exception");
}

}2、web- app1package com.example.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ShoppController {

private static final Logger logger = LoggerFactory.getLogger(ShoppController.class);

@GetMapping("/shopping")
public String hello(@RequestParam(defaultValue = "苹果") String commodity) {
    logger.info("Processing hello request for: {}", commodity);
    return "commodity, " + commodity + "!";
}

@PostMapping("/user")
public String createUser(@RequestBody String userData) {
    logger.info("Creating user with data: {}", userData);
    return "User created: " + userData;
}

@GetMapping("/error")
public String error() {
    logger.info("Triggering error");
    throw new RuntimeException("Test exception");
}

}从上面可以看到我们在web-app的应用中的hell接口中调用了web-app1的shopping接口,且web-app的接入方式是无代码入侵形式的RestTemplate,主要是依赖agent对asm对增强能实现对trace调用透传
图片
且web-app和web-app1两个进程起来时要通过-javaagent方式将agent的探针无入侵的方式接入应用中而-DappId时接入的应用id,用于跟踪tarce所在的应用和构建应用的拓扑图
图片
五、验证触发接口
图片

图片

图片
至此可以通过Agent的探针实现对应用无入侵式,实现调用链的APM 监控、构建应用的拓扑图,并切基于Agent Advice 的增强方式可以进一步实现对中间件的跟踪和观测,如接入DB的观测。