一、JavaAgent 场景为什么要留神类抵触问题?
类抵触问题并非仅存在于 JavaAgent 场景中,在 Java 场景中始终都存在,该问题通常会导致运行时触发 NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError 等异样。
从应用场景来看,基于 JavaAgent 技术所实现的工具,往往用于监控、治理等场景,并非企业外围业务程序。如果在应用时引入类抵触问题,可能会造成外围业务程序故障,得失相当,所以防止向外围业务程序引入类抵触是一个 JavaAgent 工具的根本要求。
还有一个重要起因是在 Java 利用中能够于开发态采纳依赖的升降级、对立依赖架构治理等伎俩解决该问题。但基于 JavaAgent 技术实现的工具作用于运行态,无奈在开发态就和须要被加强的 Java 利用进行对立的依赖治理,所以引入类抵触问题的可能性更大。
二、JavaAgent 场景如何解决该问题?
无论是在 Java 利用中,还是在 JavaAgent 场景,修复类抵触的逻辑都是统一的,就是防止引入会抵触的类。不同点在于基于 JavaAgent 技术实现的各式各样的工具,往往都具备业务无关性,在设计和实现之初,并不会为特定的 Java 利用类型而定制。对于 JavaAgent 程序而言,须要被字节码加强的利用即是黑盒,所以无奈像 Java 利用那样去梳理依赖构造,排除、降级依赖项,对立进行依赖架构治理。并且 JavaAgent 往往在运行时应用,所以只能通过保障依赖相对隔离的形式来防止引入抵触。
为何会产生类抵触,本文重点不在此,简略讲是因为咱们在 Java 中因为反复引入传递依赖、类的加载程序无法控制等问题,导致引入了雷同【类加载器和全限定类名 (Fully Qualified Class Name) 都雷同】但又体现不同【因为类版本不同而导致的类逻辑不统一】的类。所以为了防止抵触,咱们就须要防止在运行时引入雷同的类,如何让 JavaAgent 引入的类和宿主齐全不雷同,从全限定类名和类加载器下手才是基本:
- 基于 maven shade plugin 进行类隔离
该插件是 Maven 提供用于构建打包的插件,通过 maven-shade-plugin 的‘Relocating Classes’能力,来批改某些类的全限定类名。
此办法的原理便是通过扭转 全限定类名 来让 JavaAgent 引入的类和 Java 程序的类齐全不可能呈现雷同的状况,从根本上防止类抵触。然而咱们在应用一种框架,或者应用一种产品时,往往约定要优于配置,基于 maven-shade-plugin 通过配置去扭转 全限定类名 并不是一个简略的方法,在应用时就须要针对 JavaAgent 所波及依赖进行梳理,在 maven-shade-plugin 中进行配置,并且须要在每次依赖变更后从新筛查。对继续迭代极不敌对。
采纳上述办法也对 Debug 造成妨碍,在 Debug 过程中被重定向的类的断点将不可达,重大升高调试效率。
- 基于类加载机制进行类隔离
基于 maven-shade-plugin 批改全限定类名往往用来解决单点的类抵触问题,尽管也能做到将 JavaAgent 所引入类齐全隔离,但并不是一个好的解决方案。
基于类抵触原理,咱们还能够通过限度两个雷同全限定类名的类的加载器来让其不同,如 Tomcat 那样,通过自定义类加载器毁坏 Java 的双亲委派准则,来隔离 JavaAgent 引入的类。这样既防止了沉重的配置,也防止了依赖变更而带来的影响。但也有其弊病,在 JavaAgent 场景中往往会利用到 Java 应用程序的类,所以基于类加载器的隔离机制,往往就让开发者只能通过反射等操作实现此类逻辑,这会对性能和开发效率产生不良影响。
三、Sermant 如何做?
Sermant 是基于 Java 字节码加强技术的无代理服务网格,不仅是一个开箱即用的服务治理工具,也同样是一个易用的服务治理能力开发框架。
“把简略留给他人,把麻烦留给本人!”
Sermant 从设计之初就遵循上述重要准则,并布局了全方面的类隔离架构,利用 Java 的类加载机制对本身各模块做了充沛类隔离,让使用者和开发者无需思考因应用 JavaAgent 而导致类抵触问题,并且也针对开发者的应用场景做了优化,能够在开发中无缝应用被加强 Java 程序的类,防止因反射等行为带来的不利影响。Sermant 是如何实现的呢,下文将对 Sermant 类隔离架构进行具体解析。
1) Sermant 的类隔离架构解析
如上文所说,Sermant 不仅是个开箱即用的服务网格,也同样是一个易用的服务治理能力开发框架,服务治理能力是多样的包含但不限于流量治理、可用性治理、平安治理等,所以 Sermant 采纳插件化的架构来让用户能更灵便的接入和开发服务治理能力。
在 Sermant 的整体架构下,咱们不仅须要保障不向宿主服务引入类抵触问题,防止在开箱即用时对宿主服务造成负面影响,同时也须要保障框架与插件、插件与插件之间不会引入类抵触问题,防止插件开发者因为和其余服务治理插件产生类抵触问题而苦恼,所以 Sermant 设计了如下类隔离构造:
- SermantClassLoader,毁坏双亲委派,用于加载 Sermant 框架外围逻辑,并在 AppClassLoader 下隔离出 Sermant 的类加载模型。防止受到宿主服务本身简单类加载构造的影响,缩小应答不同类加载构造服务的适配需要。
- FrameworkClassLoader,毁坏双亲委派,次要作用是隔离 Sermant 外围能力所引入的三方依赖,防止向宿主服务及服务治理插件引入类抵触问题。目前的次要场景 ①用于隔离 Sermant 的日志零碎,防止对宿主服务的日志零碎产生影响 ②隔离 Sermant 框架的外围服务(心跳、动静配置、对立音讯网关)所需三方依赖。
- PluginClassLoader,遵循双亲委派,次要用于隔离 Sermant 各服务治理插件,防止不同服务治理插件之间产生类抵触问题。
- ServiceClassLoader,毁坏双亲委派,次要用于隔离插件中的依赖,通过该类加载器加载插件服务的相干 lib(插件服务会在插件加载时被 Sermant 初始化),开发者可任意引入三方依赖,无需关怀对插件主逻辑的影响。
其中的 PluginClassloader 和 ServiceClassloader 不仅在类隔离中起到至关重要的作用,更是一种久远的思考,为每个插件设计独立的类加载器,使得 Sermant 能够平滑的进行插件动静装置 & 卸载以及插件热更新。
2) 插件隔离的非凡之处
在上文中所述类隔离架构中,能够看到一处特地的逻辑(红框处),这也是 Sermant 中 PluginClassLoader(插件类加载器)的非凡之处,在理论应用过程中,每个插件类加载器会在其中为每个线程保护一个部分的类加载器(localLoader)。
PluginClassLoader 遵循双亲委派,在类加载过程中先委派 SermantClassLoader 加载 Sermant 的外围类,再通过本身加载插件类,当须要应用宿主服务的类时,则会委托部分类加载器(其 Parent 能够是任何类加载器,不局限于图中所批示)进行加载。用于让字节码加强的切面逻辑(Sermant 拦截器)能够获取到宿主服务所应用的类,这有利于服务治理场景,其逻辑如下图所示:
通过重写类加载器 loadClass 逻辑,在执行 Sermant 拦截器时,配置一个部分的类加载环境,让 Sermant 拦截器中的逻辑能够顺利的应用宿主服务加载的类,这样 开发服务治理插件时无需通过反射获取宿主服务的类,从而晋升服务治理能力的开发效率和最终运行时的性能,同时还防止了宿主服务和服务治理插件的类抵触。
(代码实现能够在开源仓库进行查看:)
3) 实战成果如何
因接入 JavaAgent 而导致的依赖抵触、类抵触问题乃是业界通病,但如果有 Sermant 的类加载机制加持,该问题则可从本源防止,不再让宽广 JavaAgent 的使用者和开发者深受其害!
《托付,别在 agent 中依赖 fastjson 了》所述案例,是一个因 JavaAgent 而产生的依赖抵触问题的典型场景,其利用通过 AppClassLoader 加载到了 Agent 中 fastjson 的类 FastJsonHttpMessageConverter,该类依赖 spring-web.jar 的类 GenericHttpMessageConverter,但因为 AppClassLoader 的搜寻门路中并没有 spring-web.jar(fastjson 通过 provide 形式引入),最终加载类失败。
但如基于 Sermant 开发则不会产生该问题,基于 Sermant 开发 JavaAgent 和 Spring 利用一起运行时的类隔离架构如下:
在此类加载器的构造下,有两个要害的不同:
- 因为 Sermant 扭转了类加载的构造,通过 Agent 引入的 fastjson 已不在 AppClassLoader 的搜寻门路中,因而 Agent 中的 FastJsonHttpMessageConverter 类不再会被 Spring 利用通过 AppClassLoader 加载到,从本源上防止了文中所触发的类抵触问题。
- 当运行时若 Agent 须要应用 spring-web 的类 GenericHttpMessageConverter 时,则可通过 Sermant 提供的部分类加载环境胜利通过 LaunchedUrlClassloader 胜利从 Spring 利用中获取。
正是因为此两点差别,让基于 Sermant 开发的能力能够在和利用之间进行类隔离,防止通过 JavaAgent 引入类抵触问题,同时能够在运行时应用利用所引入的类。
四、总结
Sermant 是基于 Java 字节码加强技术的无代理服务网格,其利用 Java 字节码加强技术为宿主应用程序提供服务治理性能。因深知 JavaAgent 场景中类抵触问题会造成的影响,Sermant 在设计之初便为此布局了全面的类隔离架构。经验屡次迭代,现在 Sermant 的类隔离架构已能够轻松的应答各种简单的类加载环境。
除了保障类隔离,Sermant 作为服务网格须要重点关注本身的服务治理能力对宿主服务带来的性能影响,所以也通过独有设计防止因为适度隔离带来的性能损耗。同时 Sermant 还在构建凋谢的服务治理插件开发生态,并提供高效的服务治理能力开发框架。在类隔离设计时也思考到了易用性、开发效率晋升等方面的问题。并未因为类隔离机制的存在,而升高开发的效率,增大学习曲线的平缓水平。
Sermant 作为专一于服务治理畛域的字节码加强框架,致力于提供高性能、可扩大、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、性能、体验的看护,宽泛欢送大家的退出。
- Sermant 官网: https://sermant.io
- GitHub 仓库地址: https://github.com/huaweicloud/Sermant
- 扫码退出 Sermant 社区交换群