深入探秘 OpenTelemetry Agent 奇特的 muzzle 机制
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
:
这个接口提供了一些方法用于获取helper class
以及三方类的引用信息等等。对于所有的InstrumentationModule
,这个接口都会应用一遍。但是这个接口很特殊,他没有实现类!
InstrumentationModuleMuzzle
没有在代码中直接实现这个接口,而是通过ByteBuddy
来构造了一个实现。
Agent 通过构建MuzzleCodeGenerator
实现了AsmVisitorWrapper
来完整构造了InstrumentationModuleMuzzle
的实现方法。因此虽然表面上这个接口没有用,但是通过动态字节码的构造,使得他存在了用处。
运行时检查
运行时检查也是基于ByteBuddy
实现,Agent 通过实现了AgentBuilder.RawMatcher
构造了匹配类MuzzleMatcher
。
类实现了matches
方法,并构建doesMatch
来使用编译时采集的数据来进行运行时的校验:
值得注意的是,由于 muzzle 检查的开销很大,所以它仅在 InstrumentationModule#classLoaderMatcher()
和 TypeInstrumentation#typeMatcher()
匹配器进行匹配后才执行。muzzle matcher
的结果会在每个类加载器中缓存,因此它只对整个检测模块执行一次。
总结
Otel Agent 花费了巨大的精力来构建 muzzle 体系来解决 Agent 和应用之间的类冲突,虽然很复杂,但是这部分实现对于用户是隐藏的,所以在使用时用户会觉得很友好。如果有兴趣可以自行研究一下 muzzle 的代码实现,或许会有不一样的收获。
版权声明: 本文为 InfoQ 作者【骑牛上青山】的原创文章。
原文链接:【http://xie.infoq.cn/article/5fc98d722b27a2c01b2be03a0】。文章转载请联系作者。
评论