一、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 社区交换群