关于后端:深入探秘OpenTelemetry-Agent奇特的muzzle机制

50次阅读

共计 3461 个字符,预计需要花费 9 分钟才能阅读完成。

Java Agent 存在这么一个问题,利用和 Agent 尽管执行时算是一体的,然而实际上 Agent 在 JVM 层面是以 AppClassLoader 类加载器加载的,而利用代码则不肯定。因而当 Agent 中存在利用的加强代码时,容易产生种种问题。OpenTelemetry Agent为了解决这些问题引入了非凡的机制 muzzle,本文就将向大家解说muzzle 是如何来解决相似问题的。

Muzzle 的作用

Muzzle is a safety feature of the Java agent that prevents applying instrumentation when a mismatch between the instrumentation code and the instrumented application code is detected.

简略来说,muzzle 是用来在编译时以及运行时进行类和类加载器校验的机制。在 Agent 中独自有一部分代码来实现了这个简单的能力。

至于他是怎么失效的,咱们后续慢慢说。

为什么须要 Muzzle

Muzzle 是一个查看运行时类是否匹配的机制,那么咱们为什么须要这种机制呢?

构想这么一个场景:在利用中援用到了 otel 的 sdk,版本是 1.14.0,而在 Agent 中同样援用了 otel 的 sdk,版本却是 1.15.0,那么理论中产生的抵触怎么办?

再来构想这么一个场景:如果在 Agent 中加强用户代码,然而这部分援用了某个三方 sdk,而这个 sdk 也在利用中应用到了,且版本可能不同,那又要怎么解决?

上述两个场景当然能够应用 shadow sdk,或者一股脑应用BootstrapClassLoader 来加载类来解决。然而这也会遇到其余的不拘一格的问题。所以 Opentelemetry Java Agent 提供了 muzzle 机制来一劳永逸的解决这个问题。

Muzzle 如何运作

Muzzle 分为两个局部:

  • 在编译时,muzzle 会采集应用到的的 helper class 以及第三方的 symbols(蕴含类,办法,变量等等) 援用
  • 在运行时他会校验这些援用和实际上 classpath 上援用到的类是否统一

编译时采集

编译时采集借助了 gradle 插件 muzzle-generation 来实现。

Opentelemetry Java Agent 提供了这么一个接口InstrumentationModuleMuzzle

public interface InstrumentationModuleMuzzle {Map<String, ClassRef> getMuzzleReferences();

  static Map<String, ClassRef> getMuzzleReferences(InstrumentationModule module) {if (module instanceof InstrumentationModuleMuzzle) {return ((InstrumentationModuleMuzzle) module).getMuzzleReferences();} else {return Collections.emptyMap();
    }
  }

  void registerMuzzleVirtualFields(VirtualFieldMappingsBuilder builder);

  List<String> getMuzzleHelperClassNames();

  static List<String> getHelperClassNames(InstrumentationModule module) {
    List<String> muzzleHelperClassNames =
        module instanceof InstrumentationModuleMuzzle
            ? ((InstrumentationModuleMuzzle) module).getMuzzleHelperClassNames()
            : Collections.emptyList();

    List<String> additionalHelperClassNames = module.getAdditionalHelperClassNames();

    if (additionalHelperClassNames.isEmpty()) {return muzzleHelperClassNames;}
    if (muzzleHelperClassNames.isEmpty()) {return additionalHelperClassNames;}

    List<String> result = new ArrayList<>(muzzleHelperClassNames);
    result.addAll(additionalHelperClassNames);
    return result;
  }
}

这个接口提供了一些办法用于获取 helper class 以及三方类的援用信息等等。对于所有的 InstrumentationModule,这个接口都会利用一遍。然而这个接口很非凡, 他没有实现类!

InstrumentationModuleMuzzle没有在代码中间接实现这个接口,而是通过 ByteBuddy 来结构了一个实现。

Agent 通过构建 MuzzleCodeGenerator 实现了 AsmVisitorWrapper 来残缺结构了
InstrumentationModuleMuzzle 的实现办法。因而尽管外表上这个接口没有用,然而通过动静字节码的结构,使得他存在了用途。

运行时查看

运行时查看也是基于 ByteBuddy 实现,Agent 通过实现了 AgentBuilder.RawMatcher 结构了匹配类MuzzleMatcher

类实现了 matches 办法,并构建 doesMatch 来应用编译时采集的数据来进行运行时的校验:

private boolean doesMatch(ClassLoader classLoader) {ReferenceMatcher muzzle = getReferenceMatcher();
      boolean isMatch = muzzle.matches(classLoader);

      if (!isMatch) {MuzzleFailureCounter.inc();
        if (muzzleLogger.isLoggable(WARNING)) {
          muzzleLogger.log(
              WARNING,
              "Instrumentation skipped, mismatched references were found: {0} [class {1}] on {2}",
              new Object[] {instrumentationModule.instrumentationName(),
                instrumentationModule.getClass().getName(),
                classLoader
              });
          List<Mismatch> mismatches = muzzle.getMismatchedReferenceSources(classLoader);
          for (Mismatch mismatch : mismatches) {muzzleLogger.log(WARNING, "-- {0}", mismatch);
          }
        }
      } else {if (logger.isLoggable(FINE)) {
          logger.log(
              FINE,
              "Applying instrumentation: {0} [class {1}] on {2}",
              new Object[] {instrumentationModule.instrumentationName(),
                instrumentationModule.getClass().getName(),
                classLoader
              });
        }
      }

      return isMatch;
    }

值得注意的是,因为 muzzle 查看的开销很大,所以它仅在 InstrumentationModule#classLoaderMatcher()TypeInstrumentation#typeMatcher() 匹配器进行匹配后才执行。muzzle matcher的后果会在每个类加载器中缓存,因而它只对整个检测模块执行一次。

总结

Otel Agent 破费了微小的精力来构建 muzzle 体系来解决 Agent 和利用之间的类抵触,尽管很简单,然而这部分实现对于用户是暗藏的,所以在应用时用户会感觉很敌对。如果有趣味能够自行钻研一下 muzzle 的代码实现,或者会有不一样的播种。

正文完
 0