diff --git a/README.md b/README.md index f9e9d3b637..0a49bed5ba 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,8 @@ - [JVM 体系结构](source/_posts/01.Java/01.JavaSE/06.JVM/01.JVM体系结构.md) - [JVM 内存区域](source/_posts/01.Java/01.JavaSE/06.JVM/02.JVM内存区域.md) - 关键词:`程序计数器`、`虚拟机栈`、`本地方法栈`、`堆`、`方法区`、`运行时常量池`、`直接内存`、`OutOfMemoryError`、`StackOverflowError` - [JVM 垃圾收集](source/_posts/01.Java/01.JavaSE/06.JVM/03.JVM垃圾收集.md) - 关键词:`GC Roots`、`Serial`、`Parallel`、`CMS`、`G1`、`Minor GC`、`Full GC` -- [JVM 字节码](source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` -- [JVM 类加载](source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 字节码](source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` +- [JVM 类加载](source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` - [JVM 命令行工具](source/_posts/01.Java/01.JavaSE/06.JVM/11.JVM命令行工具.md) - 关键词:`jps`、`jstat`、`jmap` 、`jstack`、`jhat`、`jinfo` - [JVM GUI 工具](source/_posts/01.Java/01.JavaSE/06.JVM/12.JVM_GUI工具.md) - 关键词:`jconsole`、`jvisualvm`、`MAT`、`JProfile`、`Arthas` - [JVM 实战](source/_posts/01.Java/01.JavaSE/06.JVM/21.JVM实战.md) - 关键词:`配置`、`调优` @@ -151,7 +151,7 @@ - [Maven 实战问题和最佳实践](source/_posts/01.Java/11.软件/01.构建/01.Maven/04.Maven实战问题和最佳实践.md) - [Maven 教程之发布 jar 到私服或中央仓库](source/_posts/01.Java/11.软件/01.构建/01.Maven/05.Maven教程之发布jar到私服或中央仓库.md) - [Maven 插件之代码检查](source/_posts/01.Java/11.软件/01.构建/01.Maven/06.Maven插件之代码检查.md) -- [Ant 简易教程](source/_posts/01.Java/11.软件/01.构建/02.Ant.md) +- [Ant 简易教程](source/_posts/01.Java/11.软件/01.构建/03.Ant.md) #### Java IDE diff --git "a/assets/Java/\346\241\206\346\236\266/SpringCloud.xmind" "b/assets/Java/\346\241\206\346\236\266/SpringCloud.xmind" new file mode 100644 index 0000000000..458c8f9d67 Binary files /dev/null and "b/assets/Java/\346\241\206\346\236\266/SpringCloud.xmind" differ diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\347\261\273\345\212\240\350\275\275.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\347\261\273\345\212\240\350\275\275.md" similarity index 100% rename from "source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\347\261\273\345\212\240\350\275\275.md" rename to "source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\347\261\273\345\212\240\350\275\275.md" diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\345\255\227\350\212\202\347\240\201.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\345\255\227\350\212\202\347\240\201.md" similarity index 100% rename from "source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\345\255\227\350\212\202\347\240\201.md" rename to "source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\345\255\227\350\212\202\347\240\201.md" diff --git a/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md b/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md index 59a51b8691..a0cb367dcf 100644 --- a/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md +++ b/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md @@ -1,5 +1,5 @@ --- -title: JavaAgent 应用指南 +title: JavaAgent date: 2022-04-08 17:29:48 categories: - Java @@ -13,7 +13,7 @@ tags: permalink: /pages/16e728/ --- -# JavaAgent 应用指南 +# JavaAgent Javaagent 是什么? @@ -36,6 +36,338 @@ premain 方法,从字面上理解,就是运行在 main 函数之前的的类 加载 Java 编程语言代理, 请参阅 java.lang.instrument ``` +## Java Agent 技术简介 + +Java Agent 直译为 Java 代理,也常常被称为 Java 探针技术。 + +Java Agent 是在 JDK1.5 引入的,是一种可以动态修改 Java 字节码的技术。Java 中的类编译后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码的信息,并且通过字节码转换器对这些字节码进行修改,以此来完成一些额外的功能。 + +Java Agent 是一个不能独立运行 jar 包,它通过依附于目标程序的 JVM 进程,进行工作。启动时只需要在目标程序的启动参数中添加-javaagent 参数添加 ClassFileTransformer 字节码转换器,相当于在 main 方法前加了一个拦截器。 + +## Java Agent 功能介绍 + +Java Agent 主要有以下功能 + +- Java Agent 能够在加载 Java 字节码之前拦截并对字节码进行修改; +- Java Agent 能够在 Jvm 运行期间修改已经加载的字节码; + +Java Agent 的应用场景 + +- IDE 的调试功能,例如 Eclipse、IntelliJ IDEA ; +- 热部署功能,例如 JRebel、XRebel、spring-loaded; +- 各种线上诊断工具,例如 Btrace、Greys,还有阿里的 Arthas; +- 各种性能分析工具,例如 Visual VM、JConsole 等; +- 全链路性能检测工具,例如 Skywalking、Pinpoint 等; + +## Java Agent 实现原理 + +在了解 Java Agent 的实现原理之前,需要对 Java 类加载机制有一个较为清晰的认知。一种是在 man 方法执行之前,通过 premain 来执行,另一种是程序运行中修改,需通过 JVM 中的 Attach 实现,Attach 的实现原理是基于 JVMTI。 + +主要是在类加载之前,进行拦截,对字节码修改 + +下面我们分别介绍一下这些关键术语: + +- **JVMTI** 就是 JVM Tool Interface,是 JVM 暴露出来给用户扩展使用的接口集合,JVMTI 是基于事件驱动的,JVM 每执行一定的逻辑就会触发一些事件的回调接口,通过这些回调接口,用户可以自行扩展 + + JVMTI 是实现 Debugger、Profiler、Monitor、Thread Analyser 等工具的统一基础,在主流 Java 虚拟机中都有实现 + +- **JVMTIAgent**是一个动态库,利用 JVMTI 暴露出来的一些接口来干一些我们想做、但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分,它一般会实现如下的一个或者多个函数: + + - Agent_OnLoad 函数,如果 agent 是在启动时加载的,通过 JVM 参数设置 + - Agent_OnAttach 函数,如果 agent 不是在启动时加载的,而是我们先 attach 到目标进程上,然后给对应的目标进程发送 load 命令来加载,则在加载过程中会调用 Agent_OnAttach 函数 + - Agent_OnUnload 函数,在 agent 卸载时调用 + +- **javaagent** 依赖于 instrument 的 JVMTIAgent(Linux 下对应的动态库是 libinstrument.so),还有个别名叫 JPLISAgent(Java Programming Language Instrumentation Services Agent),专门为 Java 语言编写的插桩服务提供支持的 + +- **instrument** 实现了 Agent_OnLoad 和 Agent_OnAttach 两方法,也就是说在使用时,agent 既可以在启动时加载,也可以在运行时动态加载。其中启动时加载还可以通过类似-javaagent:jar 包路径的方式来间接加载 instrument agent,运行时动态加载依赖的是 JVM 的 attach 机制,通过发送 load 命令来加载 agent + +- **JVM Attach** 是指 JVM 提供的一种进程间通信的功能,能让一个进程传命令给另一个进程,并进行一些内部的操作,比如进行线程 dump,那么就需要执行 jstack 进行,然后把 pid 等参数传递给需要 dump 的线程来执行 + +## Java Agent 案例 + +### 加载 Java 字节码之前拦截 + +#### App 项目 + +(1)创建一个名为 `javacore-javaagent-app` 的 maven 工程 + +```xml + + + 4.0.0 + + io.github.dunwu.javacore + javacore-javaagent-app + 1.0.1 + JavaCore :: JavaAgent :: App + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + +``` + +(2)创建一个应用启动类 + +```java +public class AppMain { + + public static void main(String[] args) { + System.out.println("APP 启动!!!"); + AppInit.init(); + } + +} +``` + +(3)创建一个模拟应用初始化的类 + +```java +public class AppInit { + + public static void init() { + try { + System.out.println("APP初始化中..."); + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} +``` + +(4)输出 + +``` +APP 启动!!! +APP初始化中... +``` + +#### Agent 项目 + +(1)创建一个名为 `javacore-javaagent-agent` 的 maven 工程 + +```java + + + 4.0.0 + + io.github.dunwu.javacore + javacore-javaagent-agent + 1.0.1 + JavaCore :: JavaAgent :: Agent + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + org.javassist + javassist + 3.26.0-GA + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + 8 + 8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + + true + + + 1.0 + io.github.dunwu.javacore.javaagent.RunTimeAgent + true + true + + + + + + + +``` + +(2)创建一个 Agent 启动类 + +```java +public class RunTimeAgent { + + public static void premain(String arg, Instrumentation instrumentation) { + System.out.println("探针启动!!!"); + System.out.println("探针传入参数:" + arg); + instrumentation.addTransformer(new RunTimeTransformer()); + } +} +``` + +这里每个类加载的时候都会走这个方法,我们可以通过 className 进行指定类的拦截,然后借助 javassist 这个工具,进行对 Class 的处理,这里的思想和反射类似,但是要比反射功能更加强大,可以动态修改字节码。 + +(3)使用 javassist 拦截指定类,并进行代码增强 + +```java +package io.github.dunwu.javacore.javaagent; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; + +public class RunTimeTransformer implements ClassFileTransformer { + + private static final String INJECTED_CLASS = "io.github.dunwu.javacore.javaagent.AppInit"; + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + String realClassName = className.replace("/", "."); + if (realClassName.equals(INJECTED_CLASS)) { + System.out.println("拦截到的类名:" + realClassName); + CtClass ctClass; + try { + // 使用javassist,获取字节码类 + ClassPool classPool = ClassPool.getDefault(); + ctClass = classPool.get(realClassName); + + // 得到该类所有的方法实例,也可选择方法,进行增强 + CtMethod[] declaredMethods = ctClass.getDeclaredMethods(); + for (CtMethod method : declaredMethods) { + System.out.println(method.getName() + "方法被拦截"); + method.addLocalVariable("time", CtClass.longType); + method.insertBefore("System.out.println(\"---开始执行---\");"); + method.insertBefore("time = System.currentTimeMillis();"); + method.insertAfter("System.out.println(\"---结束执行---\");"); + method.insertAfter("System.out.println(\"运行耗时: \" + (System.currentTimeMillis() - time));"); + } + return ctClass.toBytecode(); + } catch (Throwable e) { //这里要用Throwable,不要用Exception + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + return classfileBuffer; + } + +} +``` + +(4)输出 + +指定 VM 参数 -javaagent:F:\code\myCode\agent-test\runtime-agent\target\runtime-agent-1.0-SNAPSHOT.jar=hello,运行 AppMain + +``` +探针启动!!! +探针传入参数:hello +APP 启动!!! +拦截到的类名:io.github.dunwu.javacore.javaagent.AppInit +init方法被拦截 +---开始执行--- +APP初始化中... +---结束执行--- +运行耗时: 1014 +``` + +### 运行时拦截(JDK 1.6 及以上) + +如何实现在程序运行时去完成动态修改字节码呢? + +动态修改字节码需要依赖于 JDK 为我们提供的 JVM 工具,也就是上边我们提到的 Attach,通过它去加载我们的代理程序。 + +首先我们在代理程序中需要定义一个名字为 agentmain 的方法,它可以和上边我们提到的 premain 是一样的内容,也可根据 agentmain 的特性进行自己逻辑的开发。 + +```java +/** + * agentmain 在 main 函数开始运行后才启动(依赖于Attach机制) + */ +public class RunTimeAgent { + + public static void agentmain(String arg, Instrumentation instrumentation) { + System.out.println("agentmain探针启动!!!"); + System.out.println("agentmain探针传入参数:" + arg); + instrumentation.addTransformer(new RunTimeTransformer()); + } +} +``` + +然后就是我们需要将配置中设置,让其知道我们的探针需要加载这个类,在 maven 中设置如下,如果是 META-INF/MANIFEST.MF 文件同理。 + +```xml + +com.zhj.agent.agentmain.RunTimeAgent +``` + +这样其实我们的探针就已经改造好了,然后我们需要在目标程序的 main 方法中植入一些代码,使其可以读取到我们的代理程序,这样我们也无需去配置 JVM 的参数,就可以加载探针程序。 + +```java +public class APPMain { + + public static void main(String[] args) { + System.out.println("APP 启动!!!"); + for (VirtualMachineDescriptor vmd : VirtualMachine.list()) { + // 指定的VM才可以被代理 + if (true) { + System.out.println("该VM为指定代理的VM"); + System.out.println(vmd.displayName()); + try { + VirtualMachine vm = VirtualMachine.attach(vmd.id()); + vm.loadAgent("D:/Code/java/idea_project/agent-test/runtime-agent/target/runtime-agent-1.0-SNAPSHOT.jar=hello"); + vm.detach(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + AppInit.init(); + } +} +``` + +其中 VirtualMachine 是 JDK 工具包下的类,如果系统环境变量没有配置,需要自己在 Maven 中引入本地文件。 + +```java + + com.sun + tools + 1.8 + system + D:/Software/java_dev/java_jdk/lib/tools.jar + +复制代码 +``` + +这样我们在程序启动后再去动态修改字节码文件的简单案例就完成了。 + ## 参考资料 -- [java.lang.instrument](https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/package-summary.html) \ No newline at end of file +- [Java Agent 探针技术](https://juejin.cn/post/7086026013498408973) diff --git a/source/_posts/01.Java/01.JavaSE/06.JVM/README.md b/source/_posts/01.Java/01.JavaSE/06.JVM/README.md index 5613237d91..9ef35dea17 100644 --- a/source/_posts/01.Java/01.JavaSE/06.JVM/README.md +++ b/source/_posts/01.Java/01.JavaSE/06.JVM/README.md @@ -22,8 +22,8 @@ hidden: true - [JVM 体系结构](01.JVM体系结构.md) - [JVM 内存区域](02.JVM内存区域.md) - 关键词:`程序计数器`、`虚拟机栈`、`本地方法栈`、`堆`、`方法区`、`运行时常量池`、`直接内存`、`OutOfMemoryError`、`StackOverflowError` - [JVM 垃圾收集](03.JVM垃圾收集.md) - 关键词:`GC Roots`、`Serial`、`Parallel`、`CMS`、`G1`、`Minor GC`、`Full GC` -- [JVM 字节码](04.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` -- [JVM 类加载](05.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 类加载](04.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 字节码](05.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` - [JVM 命令行工具](11.JVM命令行工具.md) - 关键词:`jps`、`jstat`、`jmap` 、`jstack`、`jhat`、`jinfo` - [JVM GUI 工具](12.JVM_GUI工具.md) - 关键词:`jconsole`、`jvisualvm`、`MAT`、`JProfile`、`Arthas` - [JVM 实战](21.JVM实战.md) - 关键词:`配置`、`调优` @@ -40,4 +40,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git a/source/_posts/01.Java/01.JavaSE/README.md b/source/_posts/01.Java/01.JavaSE/README.md index 52235122ef..7d96578461 100644 --- a/source/_posts/01.Java/01.JavaSE/README.md +++ b/source/_posts/01.Java/01.JavaSE/README.md @@ -84,8 +84,8 @@ hidden: true - [JVM 体系结构](06.JVM/01.JVM体系结构.md) - [JVM 内存区域](06.JVM/02.JVM内存区域.md) - 关键词:`程序计数器`、`虚拟机栈`、`本地方法栈`、`堆`、`方法区`、`运行时常量池`、`直接内存`、`OutOfMemoryError`、`StackOverflowError` - [JVM 垃圾收集](06.JVM/03.JVM垃圾收集.md) - 关键词:`GC Roots`、`Serial`、`Parallel`、`CMS`、`G1`、`Minor GC`、`Full GC` -- [JVM 字节码](06.JVM/04.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` -- [JVM 类加载](06.JVM/05.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 类加载](06.JVM/04.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 字节码](06.JVM/05.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` - [JVM 命令行工具](06.JVM/11.JVM命令行工具.md) - 关键词:`jps`、`jstat`、`jmap` 、`jstack`、`jhat`、`jinfo` - [JVM GUI 工具](06.JVM/12.JVM_GUI工具.md) - 关键词:`jconsole`、`jvisualvm`、`MAT`、`JProfile`、`Arthas` - [JVM 实战](06.JVM/21.JVM实战.md) - 关键词:`配置`、`调优` @@ -128,4 +128,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git a/source/_posts/01.Java/README.md b/source/_posts/01.Java/README.md index f2c4156b31..20ae63dcf1 100644 --- a/source/_posts/01.Java/README.md +++ b/source/_posts/01.Java/README.md @@ -118,8 +118,8 @@ hidden: true - [JVM 体系结构](01.JavaSE/06.JVM/01.JVM体系结构.md) - [JVM 内存区域](01.JavaSE/06.JVM/02.JVM内存区域.md) - 关键词:`程序计数器`、`虚拟机栈`、`本地方法栈`、`堆`、`方法区`、`运行时常量池`、`直接内存`、`OutOfMemoryError`、`StackOverflowError` - [JVM 垃圾收集](01.JavaSE/06.JVM/03.JVM垃圾收集.md) - 关键词:`GC Roots`、`Serial`、`Parallel`、`CMS`、`G1`、`Minor GC`、`Full GC` -- [JVM 字节码](01.JavaSE/06.JVM/04.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` -- [JVM 类加载](01.JavaSE/06.JVM/05.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 类加载](01.JavaSE/06.JVM/04.JVM类加载.md) - 关键词:`ClassLoader`、`双亲委派` +- [JVM 字节码](01.JavaSE/06.JVM/05.JVM字节码.md) - 关键词:`bytecode`、`asm`、`javassist` - [JVM 命令行工具](01.JavaSE/06.JVM/11.JVM命令行工具.md) - 关键词:`jps`、`jstat`、`jmap` 、`jstack`、`jhat`、`jinfo` - [JVM GUI 工具](01.JavaSE/06.JVM/12.JVM_GUI工具.md) - 关键词:`jconsole`、`jvisualvm`、`MAT`、`JProfile`、`Arthas` - [JVM 实战](01.JavaSE/06.JVM/21.JVM实战.md) - 关键词:`配置`、`调优` @@ -365,4 +365,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git "a/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" "b/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" index ae65e3b833..191ae58c9f 100644 --- "a/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" +++ "b/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" @@ -44,7 +44,7 @@ Dapper 提出了一些很重要的核心概念:Trace、Span、Annonation 等 _Trace 和 Spans(图片来源于[Dapper 论文](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/papers/dapper-2010-1.pdf))_ - **Trace (追踪)** - 代表一次完整的请求。一次完整的请求是指,从客户端发起请求,记录请求流转的每一个服务,直到客户端收到响应为止。整个过程中,当请求分发到第一层级的服务时,就会生成一个全局唯一的 **Trace ID**,并且会随着请求分发到每一层级。因此,通过 **Trace ID** 就可以把一次用户请求在系统中调用的链路串联起来。 -- **Span (跨度)** - 工作的基本单元。由于每次 Trace 都可能会调用数量不定、坐标不定的多个服务,为了能够记录具体调用了哪些服务,以及调用的顺序、开始时点、执行时长等信息,每次开始调用服务前都要先埋入一个调用记录,这个记录称为一个 Span。 +- **Span (跨度)** - 链路追踪的基本单元。由于每次 Trace 都可能会调用数量不定、坐标不定的多个服务,为了能够记录具体调用了哪些服务,以及调用的顺序、开始时点、执行时长等信息,每次开始调用服务前都要先埋入一个调用记录,这个记录称为一个 Span。 - Span 的数据结构应该足够简单,以便于能放在日志或者网络协议的报文头里;也应该足够完备,起码应含有时间戳、起止时间、Trace 的 ID、当前 Span 的 ID、父 Span 的 ID 等能够满足追踪需要的信息。 - Trace 实际上都是由若干个有顺序、有层级关系的 Span 所组成一颗 Trace Tree (追踪树)。 - **Annotation**:用于业务自定义埋点数据,例如:一次请求的用户 ID,某一个支付订单的订单 ID 等。 @@ -66,7 +66,7 @@ _Trace 和 Spans(图片来源于[Dapper 论文](https://static.googleuserconte > 图片说明: > -> _每种颜色表示一个跨度(有七个跨度 - 从 A 到 G)_。 +> *每种颜色表示一个跨度(有七个跨度 - 从 A 到 G)*。 > > ``` > Trace Id = X