第二节:一文读懂链路追踪模型以及Skywalking Java探针实现原理

image.png

一、Dapper 简介

在分布式链路追踪方面,Google早在2010年针对其内部的分布式链路跟踪系统Dapper[1],发表了相关论文对分布式链路跟踪技术进行了介绍(强烈推荐阅读)。其中提出了两个基本要求。第一,拥有广泛的覆盖面。针对庞大的分布式系统,其中每个服务都需要被监控系统覆盖,即使是整个系统的一小部分没有被监控到,该链路追踪系统也可能是不可靠的。第二,提供持续的监控服务。对于链路监控系统,需要7*24小时持续保障业务系统的健康运行,保证任何时刻都可以及时发现系统出现的问题,并且通常情况下很多问题是难以复现的。根据这两个基本要求,分布式链路监控系统的有如下几个设计目标:

  • 应用级透明
    链路监控组件应该以基础通用组件的方式提供给用户,以提高稳定性,应用开发者不需要关心它们。对于Java语言来说,方法可以说是调用的最小单位,想要实现对调用链的监控埋点势必对方法进行增强。Java中对方法增强的方式有很多,比如直接硬编码、动态代理、字节码增强等等。应用级透明其实是一个比较相对的概念,透明度越高意味着难度越大,对于不同的场景可以采用不同的方式。
  • 低开销
    低开销是链路监控系统最重要的关注点,分布式系统对于资源和性能的要求本身就很苛刻,因此监控组件必须对原服务的影响足够小,将对业务主链路的影响降到最低。链路监控组件对于资源的消耗主除了体现在增强方法的消耗上,其次还有网络传输和数据存储的消耗,因为对于链路监控系统来说,想要监控一次请求势必会产生出请求本身外的额外数据,并且在请求过程中,这些额外的数据不仅会暂时保存在内存中,在分布式场景中还会伴随着该请求从上游服务传输至下游服务,这就要求产生的额外数据尽可能地少,并且在伴随请求进行网络传输的时候只保留少量必要的数据。
  • 扩展性和开放性
    无论是何种软件系统,可扩展性和开放性都是衡量其质量优劣的重要标准。对于链路监控系统这样的基础服务系统来说,上游业务系统对于链路监控系统来说是透明的,在一个规模较大的企业中,一个基础服务系统往往会承载成千上万个上游业务系统。每个业务系统由不同的团队和开发人员负责,虽然使用的框架和中间件在同一个企业中有大致的规范和要求,但是在各方面还是存在差异的。因此作为一个基础设施,链路监控系统需要具有非常好的可扩展性,除了对企业中常用中间件和框架的支撑外,还要能够方便开发人员针对特殊的业务场景进行定制化的开发。

二、数据模型

OpenTracing规范

Dapper将请求按照三个维度划分为Trace、Segment、Span三种模型,该模型已经形成了OpenTracing[2]规范。OpenTracing是为了描述分布式系统中事务的语义,而与特定下游跟踪或监控系统的具体实现细节无关,因此描述这些事务不应受到任何特定后端数据展示或者处理的影响。大的概念就不多介绍了,重点看一下Trace、Segment、Span这三种模型到底是什么。

  • Trace
    表示一整条调用链,包括跨进程、跨线程的所有Segment的集合。

  • Segment
    表示一个进程(JVM)或线程内的所有操作的集合,即包含若干个Span。

  • Span
    表示一个具体的操作。Span在不同的实现里可能有不同的划分方式,这里介绍一个比较容易理解的定义方式:

  1. Entry Span:入栈Span。Segment的入口,一个Segment有且仅有一个Entry Span,比如HTTP或者RPC的入口,或者MQ消费端的入口等。
  2. Local Span:通常用于记录一个本地方法的调用。
  3. Exit Span:出栈Span。Segment的出口,一个Segment可以有若干个Exit Span,比如HTTP或者RPC的出口,MQ生产端,或者DB、Cache的调用等。

按照上面的模型定义,一次用户请求的调用链路图如下所示:


image.png

唯一id

每个请求有唯一的id还是很必要的,那么在海量的请求下如何保证id的唯一性并且能够包含请求的信息?阿里的 Eagleeye traceId 的逻辑:


image.png

根据这个id,我们可以知道这个请求在2022-10-18 10:10:40发出,被11.15.148.83机器上进程号为14031的Nginx(对应标识位e)接收到。其中的四位原子递增数从0-9999,目的是为了防止单机并发造成traceId碰撞。

关系描述

将请求划分为Trace、Segment、Span三个层次的模型后,如何描述他们之间的关系?
从【OpenTracing规范】一节的调用链路图中可以看出,Trace、Segment可以作为整个调用链路中的逻辑结构,而Span才是真正串联起整个链路的单元,系统可以通过若干个Span串联起整个调用链路。

在Java中,方法是以入栈、出栈的形式进行调用,那么系统在记录Span的时候就可以通过模拟出栈、入栈的动作来记录Span的调用顺序,不难发现最终一个链路中的所有Span呈现树形关系,那么如何描述这棵Span树?阿里的Eagleeye中的设计很巧妙,EagleEye设计了RpcId来区别同一个调用链下多个网络调用的顺序和嵌套层次。如下图所示:

image.png

RpcId用0.X1.X2.X3.....Xi来表示,根节点的RpcId固定从0开始,id的位数("."的数量)表示了Span在这棵树中的层级,Id最后一位表示了Span在这一层级中的顺序。那么给定同一个Trace中的所有RpcId,便可以很容易还原出一个完成的调用链:


- 0
  - 0.1
    - 0.1.1
    - 0.1.2
      - 0.1.2.1
  - 0.2
    - 0.2.1
  - 0.3
    - 0.3.1
      - 0.3.1.1
    - 0.3.2

跨进程传输

再进一步,在整个调用链的收集过程中,不可能将整个Trace信息随着请求携带到下个应用中,为了将跨进程传输的trace信息减少到最小,每个应用(Segment)中的数据一定是分段收集的,这样在实现下跨Segment的过程中需要携带traceId和rpcid两个简短的信息即可。在服务端收集数据时,数据自然也是分段到达服务端的,但由于种种原因分段数据可能存在乱序和丢失的情况:

image.png

如上图所示,收集到一个Trace的数据后,通过rpcid即可还原出一棵调用树,当出现某个Segment数据缺失时,可以用第一个子节点替代。

三、数据埋点

如何进行方法增强(埋点)是分布式链路追系统的关键因素,在Dapper提出的要求中可以看出,方法增强同时要满足应用级透明和低开销这两个要求。之前我们提到应用级透明其实是一个比较相对的概念,透明度越高意味着难度越大,对于不同的场景可以采用不同的方式。

  • SDK手动编码插桩
    这个需要团队有非常明确的开发使用规范,明确定义好监控的覆盖范围,同时也需要专门的维护团队来开发。对于不能实现字节码增强的开发语言,这也是最好的一种实现方式,在维护性,性能消耗上有一定的优势,缺点就是耗时耗力,对于老应用的改造很麻烦。

  • 字节码增强

对于可以实现自动化的插桩的JVM服务,Skywalking采用如下的开发模式:


image.png

Skywalking提供了核心的字节码增强能力和相关的扩展接口,对于系统中使用到的中间件可以使用官方或社区提供的插件打包后植入应用进行埋点,如果没有的话甚至可以自己开发插件实现埋点。Skywalking采用字节码增强的方式进行埋点,下面简单介绍字节码增强的相关知识和Skywalking的相关实现。

字节码增强的类库:

Java提供了很多字节码增强类库,比如大家耳熟能详的cglib、Javassist,原生的Jdk Proxy还有底层的ASM等。在2014年,一款名为Byte Buddy[3]的字节码增强类库横空出世,并在2015年获得Duke's Choice award。Byte Buddy兼顾高性能、易用、功能强大3个方面,下面是摘自其官网的一张常见字节码增强类库性能比较图(单位: 纳秒):

image.png

上图中的对比项我们可以大致分为两个方面:生成快速代码(方法调用、父类方法调用)和快速生成代码(简单类创建、接口实现、类型扩展),我们理所应当要优先选择前者。从数据可以看出Byte Buddy在纳秒级的精度下,在方法调用和父类方法调用上和基线基本没有差距,而位于其后的是cglib。
Byte Buddy和cglib有较为出色的性能得益于它们底层都是基于ASM构建,如果将ASM也加入对比那么它的性能一定是最高的。但是用过ASM的同学虽然不一定能感受到它的高性能,但一定能感受到它噩梦般的开发体验:

mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("begin of sayhello().");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

四、Skywalking 具体案例分析

介绍了这么多,下面结合Skywalking中使用Byte Buddy的案例和大家一起体验下字节码增强的开发过程,其中只简单介绍相关主流程代码,各种细节就不介绍了。

插件模型:

Skywalking为开发者提供了简单易用的插件接口,对于开发者来说不需要知道怎么增强方法的字节码,只需要关心以下几点:

  • 要增强哪个类的哪个方法?
    Skywalking提供了ClassMatch,支持各种类、方法的匹配方式。包括类名、前缀、正则、注解等方式的匹配,除此之外还提供了与、或、非逻辑链接,以支持用户通过各种方式精确定位到一个具体的方法。我们看一个插件中的代码:
@Override
protected ClassMatch enhanceClass() {
    return ClassAnnotationMatch.byClassAnnotationMatch(getEnhanceAnnotations());
}

这段逻辑表示需要增强带 getEnhanceAnnotations 方法返回的注解的方法的字节码。ClassMatch通过Builder模式提供用户流式编程的方式,最终Skywalking会将用户提供的一串ClassMatch构建出一个内部使用的类匹配逻辑。
这个抽象方法其中一个实现类如下:

public static final String ENHANCE_ANNOTATION = "org.springframework.stereotype.Controller";

@Override
protected String[] getEnhanceAnnotations() {
    return new String[] {ENHANCE_ANNOTATION};
}

其实就是拦截所有 SpringMVC 中的 Controller

  • 需要添加/修改什么逻辑?
    知道了需要增强哪个类的哪个方法,那下一步就是如何增强。Java中的方法可以分为静态方法、实例方法和构造方法三类方法,Skywalking对于这三种方法的增强逻辑为用户提供了不同的扩展点:


    image.png

以实例方法为例,Skywalking提供了如下实例方法拦截器:

/**
 * A interceptor, which intercept method's invocation. The target methods will be defined in {@link
 * ClassEnhancePluginDefine}'s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine}
 */
public interface InstanceMethodsAroundInterceptor {
    /**
     * 在方法执行前被调用
     *
     * @param result change this result, if you want to truncate the method.
     */
    void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable;

    /**
     * 在方法执行后被调用
     *
     * @param ret the method's original return value. May be null if the method triggers an exception.
     * @return the method's actual return value.
     */
    Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable;

    /**
     * 在方法抛出异常时被调用
     *
     * @param t the exception occur.
     */
    void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
        Class<?>[] argumentsTypes, Throwable t);
}

开发者通过实现该接口即可对一个实例方法进行逻辑扩展(字节码增强)。方法参数列表中的第一个类型为EnhancedInstance的参数其实就是当前对象(this),Skywalking中所有实例方法或构造方法被增强的类都会实现EnhancedInstance接口。
假设我们有一个Controller,里面只有一个sayHello方法返回"Hello",经过Skywalking增强后,反编译一下它被增强后的字节码文件:


image.png

可以看到:

  1. Skywalking在其中插入了一个名为_$EnhancedClassField_ws的字段,开发者在某些场合可以合理利用该字段存储一些信息。比如存储Spring MVC中Controller的跟路径,或者Jedis、HttpClient链接中对端信息等。
  2. 原来的syHello方法名被修改了但仍保存下来,并且新生成了一个增强后的sayHello方法,静态代码块里将经过字节码增强后的sayHello方法存入缓存字段。

增强的前置条件是什么?

在某些时候,并不是只要引入了对应插件就一定会对相关的代码进行字节码增强。比如我们想对Spring MVC的Controller进行埋点,我们使用的是Spring 4.x版本,但是插件却是 5.x 版本的,如果直接对源码进行增强可能会因为版本的差别带来意料之外的问题。Skywalking提供了一种witness机制,简单来说就是当我们的代码中存在指定的类或方式时,当前插件才会进行字节码增强。比如Spring 4.x版本中需要witness这两个类:

public abstract class AbstractSpring4Instrumentation extends ClassInstanceMethodsEnhancePluginDefine {
    public static final String WITHNESS_CLASSES = "org.springframework.cache.interceptor.SimpleKey";

    @Override
    protected String[] witnessClasses() {
        return new String[] {
            WITHNESS_CLASSES,
            "org.springframework.cache.interceptor.DefaultKeyGenerator"
        };
    }
}

如果粒度不够,还可以对方法进行witness。比如Elastic Search 6.x版本中witness了这个方法

@Override
protected String[] witnessClasses() {
    return new String[] {Constants.TASK_TRANSPORT_CHANNEL_WITNESS_CLASSES};
}

@Override
protected List<WitnessMethod> witnessMethods() {
    return Collections.singletonList(new WitnessMethod(
        Constants.SEARCH_HITS_WITNESS_CLASSES,
        named("getTotalHits").and(takesArguments(0)).and(returns(long.class))
    ));
}

// public static final String SEARCH_HITS_WITNESS_CLASSES = "org.elasticsearch.search.SearchHits";

意思就是SearchHits类中必须有名为getTotalHits、参数列表为空并且返回long的方法。

除了上面的扩展点外,Skywalking还支持对jdk核心类库的字节码增强,比如对Callable和Runnable进行增强已支持异步模式下的埋点透传。这就需要和BootstrapClassLoader打交道了,Skywalking帮我们完成了这些复杂的逻辑。Skywalking Agent部分整体的模型如下图所示:

image.png

左侧SPI部分是Skywalking暴露的插件规范接口,开发者根据这些接口实现插件。右侧Core部分负责加载插件并且利用Byte Buddy提供的字节码增强逻辑对应用中指定类和方法的字节码进行增强

主流程源码:

上面的流程主要做了两件事:
1、从指定的目录加载所有插件到内存中;
2、构建Byte Buddy核心的AgentBuilder插桩到JVM的Instrumentation API上,包括需要增强哪些类以及核心的增强逻辑Transformer。

private static class Transformer implements AgentBuilder.Transformer {
  private PluginFinder pluginFinder;

  Transformer(PluginFinder pluginFinder) {
    this.pluginFinder = pluginFinder;
  }

  /**
   * 这个方法在类加载的过程中会由JVM调用(Byte Buddy做了封装)
   * @param builder       原始类的字节码构建器
   * @param typeDescription  类描述信息
   * @param classLoader      这个类的类加载器
    * @param module         jdk9中模块信息
    * @return           修改后的类的字节码构建器
     */
  @Override
  public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder,
                      final TypeDescription typeDescription,
                      final ClassLoader classLoader,
                      final JavaModule module) {
    LoadedLibraryCollector.registerURLClassLoader(classLoader);
    // 根据类信息找到针对这个类进行字节码增强的插件,可能有多个
    List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription);
    if (pluginDefines.size() > 0) {
      DynamicType.Builder<?> newBuilder = builder;
      EnhanceContext context = new EnhanceContext();
      for (AbstractClassEnhancePluginDefine define : pluginDefines) {
        // 调用插件的define方法得到新的字节码
        DynamicType.Builder<?> possibleNewBuilder = define.define(
          typeDescription, newBuilder, classLoader, context);
        if (possibleNewBuilder != null) {
          newBuilder = possibleNewBuilder;
        }
      }
      // 返回增强后的字节码给JVM,完成字节码增强
      return newBuilder;
    }
    return builder;
  }
}

JVM在类加载的时候会触发JVM内置事件,回调Transformer传入原始类的字节码、类加载器等信息,从而实现对字节码的增强。其中的AbstractClassEnhancePluginDefine就是一个插件的抽象。


public abstract class AbstractClassEnhancePluginDefine {
  public DynamicType.Builder<?> define(TypeDescription typeDescription, DynamicType.Builder<?> builder,
                     ClassLoader classLoader, EnhanceContext context) throws PluginException {
    // witness机制
    WitnessFinder finder = WitnessFinder.INSTANCE;

    //通过类加载器找witness类,没有就直接返回,不进行字节码的改造
    String[] witnessClasses = witnessClasses();
    if (witnessClasses != null) {
      for (String witnessClass : witnessClasses) {
        if (!finder.exist(witnessClass, classLoader)) {
          return null;
        }
      }
    }

    //通过类加载器找witness方法,没有就直接返回,不进行字节码的改造
    List<WitnessMethod> witnessMethods = witnessMethods();
    if (!CollectionUtil.isEmpty(witnessMethods)) {
      for (WitnessMethod witnessMethod : witnessMethods) {
        if (!finder.exist(witnessMethod, classLoader)) {
          return null;
        }
      }
    }

    // enhance开始修改字节码
    DynamicType.Builder<?> newClassBuilder = this.enhance(typeDescription, builder, classLoader, context);

    // 修改完成,返回新的字节码
    context.initializationStageCompleted();
    return newClassBuilder;
  }

  protected DynamicType.Builder<?> enhance(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder,
                       ClassLoader classLoader, EnhanceContext context) throws PluginException {
    // 增强静态方法
    newClassBuilder = this.enhanceClass(typeDescription, newClassBuilder, classLoader);
    // 增强实例方法& 构造方法
    newClassBuilder = this.enhanceInstance(typeDescription, newClassBuilder, classLoader, context);
    return newClassBuilder;
  }
}

通过witness机制检测满足条件后,对静态方法、实例方法和构造方法进行字节码增强。我们以实例方法和构造方法为例:

public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine {
  protected DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,
                           DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader,
                           EnhanceContext context) throws PluginException {
    // 获取插件定义的构造方法拦截点ConstructorInterceptPoint
    ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints();
    // 获取插件定义的实例方法拦截点InstanceMethodsInterceptPoint
    InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints();
    String enhanceOriginClassName = typeDescription.getTypeName();
    // 非空校验
    boolean existedConstructorInterceptPoint = false;
    if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) {
      existedConstructorInterceptPoint = true;
    }
    boolean existedMethodsInterceptPoints = false;
    if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {
      existedMethodsInterceptPoints = true;
    }
    if (!existedConstructorInterceptPoint && !existedMethodsInterceptPoints) {
      return newClassBuilder;
    }

    // 这里就是之前提到的让类实现EnhancedInstance接口,并添加_$EnhancedClassField_ws字段
    if (!typeDescription.isAssignableTo(EnhancedInstance.class)) {
      if (!context.isObjectExtended()) {
        // Object类型、private volatie修饰符、提供方法进行访问
        newClassBuilder = newClassBuilder.defineField(
          "_$EnhancedClassField_ws", Object.class, ACC_PRIVATE | ACC_VOLATILE)
          .implement(EnhancedInstance.class)
          .intercept(FieldAccessor.ofField("_$EnhancedClassField_ws"));
        context.extendObjectCompleted();
      }
    }

    // 构造方法增强
    if (existedConstructorInterceptPoint) {
      for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) {
        // jdk核心类
        if (isBootstrapInstrumentation()) {
          newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher())
            .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration()
                                  .to(BootstrapInstrumentBoost
                                  .forInternalDelegateClass(constructorInterceptPoint
                                                // 非jdk核心类                                                                                                         .getConstructorInterceptor()))));
             } else {
          // 找到对应的构造方法,并通过插件自定义的InstanceConstructorInterceptor进行增强
          newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher())
            .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration()
                                  .to(new ConstructorInter(constructorInterceptPoint
                                               .getConstructorInterceptor(), classLoader))));
        }
      }
    }

    // 实例方法增强
    if (existedMethodsInterceptPoints) {
      for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) {
        // 找到插件自定义的实例方法拦截器InstanceMethodsAroundInterceptor
        String interceptor = instanceMethodsInterceptPoint.getMethodsInterceptor();
        // 这里在插件自定义的匹配条件上加了一个【不为静态方法】的条件
        ElementMatcher.Junction<MethodDescription> junction = not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher());

        // 需要重写入参
        if (instanceMethodsInterceptPoint.isOverrideArgs()) {
          // jdk核心类
          if (isBootstrapInstrumentation()) {
            newClassBuilder = newClassBuilder.method(junction)
              .intercept(MethodDelegation.withDefaultConfiguration()
                     .withBinders(Morph.Binder.install(OverrideCallable.class))
                     .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor)));
            // 非jdk核心类 
          } else {
              newClassBuilder = newClassBuilder.method(junction)
                .intercept(MethodDelegation.withDefaultConfiguration()
                       .withBinders(Morph.Binder.install(OverrideCallable.class))
                       .to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader)));
          }
        // 不需要重写入参
        } else {
          // jdk核心类    
          if (isBootstrapInstrumentation()) {
              newClassBuilder = newClassBuilder.method(junction)
                .intercept(MethodDelegation.withDefaultConfiguration()
                       .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor)));
          // 非jdk核心类                                                                   
          } else {
            // 找到对应的实例方法,并通过插件自定义的InstanceMethodsAroundInterceptor进行增强
            newClassBuilder = newClassBuilder.method(junction)
                .intercept(MethodDelegation.withDefaultConfiguration()
                       .to(new InstMethodsInter(interceptor, classLoader)));
          }
        }
      }
    }

    return newClassBuilder;
  }
} 

根据是否要重写入参、是否是核心类走到不同的逻辑分支,大致的增强逻辑大差不差,就是根据用户自定义的插件找到需要增强的方法和增强逻辑,利用Byte Buddy类库进行增强。

用户通过方法拦截器实现增强逻辑,但是它是面向用户的,并不能直接用来进行字节码增强,Skywalking加了一个中间层来连接用户逻辑和Byte Buddy类库。上述代码中的XXXInter便是中间层,比如针对实例方法的InstMethodsInter:


image.png

InstMethodsInter封装用户自定义的逻辑,并且对接ByteBuddy的核心类库,当执行到被字节码增强的方法时会执行InstMethodsInter的intercept方法(可以和上面反编译被增强后类的字节码文件进行对比):

public class InstMethodsInter {
    private static final ILog LOGGER = LogManager.getLogger(InstMethodsInter.class);

    // 用户在插件中定义的实例方法拦截器
    private InstanceMethodsAroundInterceptor interceptor;

    public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) {
        try {
            // 加载用户在插件中定义的实例方法拦截器
            interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);
        } catch (Throwable t) {
            throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t);
        }
    }

    /**
     * 当执行被增强方法时,会执行该intercept方法
     *
     * @param obj          实例对象(this)
     * @param allArguments 方法入参
     * @param method       参数描述
     * @param zuper        原方法调用的句柄
    *  @param method       被增强后的方法的引用 
     * @return             方法返回值
     */
    @RuntimeType
    public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable<?> zuper,
        @Origin Method method) throws Throwable {
        EnhancedInstance targetObject = (EnhancedInstance) obj;

        MethodInterceptResult result = new MethodInterceptResult();
        try {
            // 拦截器前置逻辑
            interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result);
        } catch (Throwable t) {
            LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName());
        }

        Object ret = null;
        try {
            // 是否中断方法执行
            if (!result.isContinue()) {
                ret = result._ret();
            } else {
                // 执行原方法
                ret = zuper.call();
                // 为什么不能走method.invoke?因为method已经是被增强后方法,调用就死循环了!
                // 可以回到之前的字节码文件查看原因,看一下该intercept执行的时机
            }
        } catch (Throwable t) {
            try {
                 // 拦截器异常时逻辑
                interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t);
            } catch (Throwable t2) {
                LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName());
            }
            throw t;
        } finally {
            try {
                // 拦截器后置逻辑
                ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret);
            } catch (Throwable t) {
                LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName());
            }
        }
        return ret;
    }
}

上述逻辑其实就是下图中红框中的逻辑:


image.png

Byte Buddy提供了声明式方式,通过几个注解就可以实现字节码增强逻辑。

下一节,我们将来聊一聊 Skywalking 如何实现高效的数据传输。

参考链接:
[1]:https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/papers/dapper-2010-1.pdf
[2]https://github.com/opentracing-contrib/opentracing-specification-zh/blob/master/specification.md
[3]https://bytebuddy.net/#/
[4]https://mp.weixin.qq.com/s/3ONVrA2_UmM9qbOPdGOrxA

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容