作者:华为云高级软件工程师 栾文飞
一、概述
Sermant 是基于 Java 字节码加强技术的无代理服务网格,其利用 Java 字节码加强技术,为宿主应用程序提供服务治理性能,以解决大规模微服务场景中的服务治理问题,通过 Java 字节码加强技术,能够非侵入的提供服务治理能力。在以往版本中,Sermant 通过配置 -javaagent 指令在微服务启动时接入服务治理能力,当须要接入及卸载 Sermant 时都须要通过重新启动微服务来实现。但从 1.2.0 版本开始,Sermant 实现了在服务不停机状态下进行装置和卸载的能力,为服务治理能力带来全新接入体验。本文将会对这种动静接入的机制,从技术根底到 Sermant 设计进行一次深入分析。
二、JavaAgent 加载形式
首先介绍一下 JavaAgent 的不同接入形式,这是 Sermant 实现动静接入能力的技术根底。Java 中 Instrumentation API 提供了一种批改字节码的机制,利用该 API,能够通过批改字节码的形式来改变程序的行为,而不必涉及程序的源码。JavaAgent 为 Instrumentation API 的客户端,通过 JavaAgent 能够调用 API 进行字节码的操作,其提供了两种加载形式给开发者重载:
动态加载:利用 premain,在应用程序启动时加载 JavaAgent 称为动态加载,动态加载会在启动时在执行任何代码之前批改字节码。
动态加载时,字节码加强是在类加载时产生的,当 Java 程序启动时,类加载过程中所有被加载的类都会通过 JavaAgent 所定义的类文件转换器的解决。
动静加载:利用 agentmain 通过 Java Attach API 将 JavaAgent 加载到已运行的 JVM 中,动静加载能够通过字节码重转换的形式在运行时批改字节码。
动静加载时,和动态加载不同的是,此时 JVM 已在运行,指标类已被加载,就不能像动态加载时一样触发字节码加强过程,在应用动静加载的过程中,往往会通过 Instrumentation API 来触发指标类(当然也能够指定所有已被加载的类)的重转换过程,在重转换过程中就会触发到 Agent 构建的类文件转换器,从而实现字节码加强过程。
动静加载形式为 JavaAgent 提供了在 JVM 运行时接入的能力,但通过类重转换来触发字节码加强绝对于在类加载时加强有肯定的局限性,例如不能在加强时批改类的继承关系,不能为类增加动态代码块,不能加强内存中和资源文件中字节码不统一的类等,这些也是在应用动静加载和多 JavaAgent 场景中常见的问题,综上,两种加载形式各有利弊,能够在应用时依照业务场景抉择。
三、Sermant 热插拔能力关键问题分析
在理解技术根底后,咱们能轻易的想到,实践上基于 JavaAgent 的动静加载形式,只须要在应用 Sermant 时,将通过 premain 形式启动改为通过 agentmain 形式启动,就能够将微服务治理能力动静的接入到微服务中,做到微服务零侵入、微服务不停机的状态下接入服务治理能力,但通往后方的路上总是充斥了阻碍:
3.1 如何保障动静装置过程中重转换可顺利执行?
这个问题的呈现,本源在于 JavaAgent 通过 agentmain 形式加载到已运行的 JVM 中时,不同于动态加载,会在类首次被加载时实现字节码的转换,动静加载时一些须要被字节码加强类曾经实现了类加载过程,这时候须要应用 Instrumentation 提供的类重转换(retransform classes)能力来批改字节码,在 Instrumentation 的 Javadoc 中对于这个能力有这样一段形容:
“The retransformation must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance.(重转换过程中,咱们不能新增、删除或者重命名字段和办法,不能更改办法的签名,不能更改类的继承。)”
从中能够看出,在引入动静加载能力前,优先要保障字节码加强时,不能够有上述内容中所形容的限度操作。
不过 Sermant 不太须要放心这个问题,因为这种限度不仅仅在动静加载时会触发,在多个 JavaAgent 同时应用时也可能会触发,能够参考 Sermant 团队的另一篇文章:《记一次多个 JavaAgent 同时应用的类加强抵触问题及剖析》。为了保障在多 Agent 场景下的兼容性,Sermant 的字节码加强模板严格遵循 Instrumentation API 的限度,因而 Sermant 在兼容性上的不断改进过程中无心插柳,帮忙动静加载能力铺平了路。
3.2 如何保障在服务治理插件装置和卸载时不相互影响?
Sermant 的设计中,通过字节码加强引入的服务治理能力,是通过在指标办法上增加服务治理性能切面来实现的,每一个服务治理插件,通过一系列切面的配合来达成最终的服务治理成果。不同的服务治理性能,可能会对同一个指标办法进行解决。但并不会对同一个办法进行屡次字节码加强,而是通过一次字节码加强织入调度切面(onMethodEnter、onMethodExit 等),通过该切面对相干的服务治理能力(通过拦截器实现,每一个切面会对应一个拦截器的列表)进行调度:
对于服务治理能力的调度逻辑咱们在另一篇文章《开发者能力机制解析,玩转 Sermant 开发》有讲过,本篇不再赘述。
基于框架的根本设计,就须要思考两个问题,当插件在动静装置时,如何保障不反复字节码加强?当插件卸载时,如何保障不会导致有雷同指标办法的插件生效。
- 装置时如何保障不反复执行字节码加强?
在字节码加强开发过程中,类文件转换器(ClassFileTransformer)是肯定会接触到的概念,开发者须要基于该转换器来进行字节码的解决。在大多数的字节码加强框架中,都会对其进行封装,用于升高字节码解决的难度。Sermant 基于 ByteBuddy 提供的类文件转换器实现了一种可重入的类转换器,在插件动静装置时,尽管指标办法曾经被已装置的插件加强过了,但此时还是会触发类文件转换(因为动静装置插件的过程是独立的),当触发类文件转换时,所有相干的类文件转换器都会被唤醒,再次触发类文件转换过程。每次可重入类转换器被唤醒时,将产生以下行为:
在 Sermant 中保护了一个针对指标办法的字节码加强锁(AdviceKey 锁),即针对每一个指标办法,保护了 1 个信号量当做锁,用于让各类文件转换器来查看指标办法的字节码加强状态,当指标办法对应的类被类转换时,就会触发 Sermant 所提供的类文件转换器,此时类文件转换器将尝试获取针对指标办法的信号量,如果能获取信号量,则执行对指标办法的字节码加强,如果不能获取,则不执行字节码加强。
基于字节码加强锁,在转换器触发时,次要有两条门路能够走,类文件转换器会通过指标办法的 AdviceKey(类名 + 办法 hash+ 类加载器组成的一个惟一示意,用于示意字节码加强的指标) 来查看其所关联的锁,判断以后指标办法是否已被 Sermant 进行过字节码加强(织入拦截器调度的切面):
- 能获取锁,阐明未被加强:则以后文件转换器获取以后 AdviceKey 所关联的锁,将其获取的锁通过其对应的插件来保护,并且执行字节码加强,将服务治理所需的拦截器放入该 AdviceKey 所对应的拦截器列表;
- 不能获取锁,阐明已被加强:则只将拦截器放入该 AdviceKey 对应的拦截器列表中,不执行字节码加强。
通过上述机制,就能够保障 Sermant 在装置不同服务治理插件时,不会进行反复的字节码加强,防止无端的性能和资源损耗。
- 卸载时如何保障不会导致其余插件生效?
当插件须要卸载时,会再次触发相干指标类的重转换,与装置时不同的是,这次须要被卸载的插件开释本身曾经持有的 AdviceKey 锁。开释锁后,触发指标类重转换时,指标类所对应的各个插件的类文件转换器将会再次触发和装置时雷同的流程:
在这个过程中,未被卸载的插件所提供的对指标类的类文件转换器,会在指标类重转换时,再次触发,并且只会经验获取锁和字节码加强的过程。这样就保障,如果还有插件须要对该指标办法进行字节码加强时,能够取得指标办法所对应的锁,不会因为指标办法的交加而导致其余插件能力生效。
四、总结
本篇文章对 Sermant 的热插拔能力的外围机制进行了解析,心愿能够为开发者及使用者在开发或应用相干能力时带来更多的灵感和便当。更多的热插拔能力介绍能够参考官网相干文档,Sermant Agent 使用手册,后续咱们也会针对热插拔实用的场景进行进一步分享,敬请期待。
Sermant 作为专一于服务治理畛域的字节码加强框架,致力于提供高性能、可扩大、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、性能、体验的看护,宽泛欢送大家的退出。
Sermant 官网:https://sermant.io
GitHub 仓库地址:https://github.com/huaweicloud/Sermant
扫码退出 Sermant 社区交换群