本文分享自华为云社区《服务运行时动态挂载JavaAgent和插件——Sermant热插拔能力解析》,作者:华为云高级软件工程师 栾文飞

一、概述

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进行过字节码增强(织入拦截器调度的切面):

  1. 能获取锁,说明未被增强:则当前文件转换器获取当前AdviceKey所关联的锁,将其获取的锁通过其对应的插件来维护,并且执行字节码增强,将服务治理所需的拦截器放入该AdviceKey所对应的拦截器列表;
  2. 不能获取锁,说明已被增强:则只将拦截器放入该AdviceKey对应的拦截器列表中,不执行字节码增强。

通过上述机制,就可以保证Sermant在安装不同服务治理插件时,不会进行重复的字节码增强,避免无端的性能和资源损耗。

  • 卸载时如何保证不会导致其他插件失效?

当插件需要卸载时,会再次触发相关目标类的重转换,与安装时不同的是,这次需要被卸载的插件释放自身已经持有的AdviceKey锁。释放锁后,触发目标类重转换时,目标类所对应的各个插件的类文件转换器将会再次触发和安装时相同的流程:

在这个过程中,未被卸载的插件所提供的对目标类的类文件转换器,会在目标类重转换时,再次触发,并且只会经历获取锁和字节码增强的过程。这样就保证,如果还有插件需要对该目标方法进行字节码增强时,可以获得目标方法所对应的锁,不会因为目标方法的交集而导致其他插件能力失效。

四、总结

本篇文章对Sermant的热插拔能力的核心机制进行了解析,希望可以为开发者及使用者在开发或使用相关能力时带来更多的灵感和便利。更多的热插拔能力介绍可以参考官网相关文档,Sermant Agent使用手册,后续我们也会针对热插拔适用的场景进行进一步分享,敬请期待。

Sermant作为专注于服务治理领域的字节码增强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、功能、体验的看护,广泛欢迎大家的加入。

点击关注,第一时间了解华为云新鲜技术~

解析Sermant热插拔能力:服务运行时动态挂载JavaAgent和插件的更多相关文章

  1. .NET6运行时动态更新限流阈值

    昨天博客园撑不住流量又崩溃了,很巧正在编写这篇文章,于是产生一个假想:如果博客园用上我这个限流组件会怎么样呢? 用户会收到几个429错误,并且多刷新几次就看到了内容,不会出现完全不可用. 还可以降低查 ...

  2. 使用javassist运行时动态重新加载java类及其他替换选择

    在不少的情况下,我们需要对生产中的系统进行问题排查,但是又不能重启应用,java应用不同于数据库的存储过程,至少到目前为止,还不能原生的支持随时进行编译替换,从这种角度来说,数据库比java的动态性要 ...

  3. 解决 Retrofit 多 BaseUrl 及运行时动态改变 BaseUrl ?

    原文地址: juejin.im/post/597856- 解决Retrofit多BaseUrl及运行时动态改变BaseUrl(一) 解决Retrofit多BaseUrl及运行时动态改变BaseUrl( ...

  4. C# 在运行时动态创建类型

    C# 在运行时动态的创建类型,这里是通过动态生成C#源代码,然后通过编译器编译成程序集的方式实现动态创建类型 public static Assembly NewAssembly() { //创建编译 ...

  5. LINQ to SQL 运行时动态构建查询条件

    在进行数据查询时,经常碰到需要动态构建查询条件.使用LINQ实现这个需求可能会比以前拼接SQL语句更麻烦一些.本文介绍了3种运行时动态构建查询条件的方法.本文中的例子最终实现的都是同一个功能,从Nor ...

  6. 运行时动态库:not found 及介绍-linux的-Wl,-rpath命令

    ---此文章同步自我的CSDN博客--- 一.运行时动态库:not found   今天在使用linux编写c/c++程序时,需要用到第三方的动态库文件.刚开始编译完后,运行提示找不到动态库文件.我就 ...

  7. C++高效安全的运行时动态类型转换

    关键字:static_cast,dynamic_cast,fast_dynamic_cast,VS 2015. OS:Window 10. C++类之间类型转换有:static_cast.dynami ...

  8. 转: gcc 指定运行时动态库路径

    gcc 指定运行时动态库路径 Leave a reply 由于种种原因,Linux 下写 c 代码时要用到一些外部库(不属于标准C的库),可是由于没有权限,无法将这写库安装到系统目录,只好安装用户目录 ...

  9. [转] Java运行时动态生成class的方法

    [From] http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000 廖雪峰 / 编 ...

  10. 运行时动态伪造vsprintf的va_list

    运行时动态伪造vsprintf的va_list #include <stdio.h> int main() { char* m = (char*) malloc(sizeof(int)*2 ...

随机推荐

  1. 每天学五分钟 Liunx 001 | 用户及用户组

    Liunx 文件权限 [root@controller-0 ~]# ll -al heihei -rw-r--r--. 1 root root 0 Mar 3 07:39 heihei 第一列 -rw ...

  2. 使用 Docker 安装 MongoDB 数据库

    by emanjusaka from https://www.emanjusaka.top/2024/01/docker-create-mongo-db 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请 ...

  3. Java21 + SpringBoot3集成Spring Data JPA

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  4. IBM jca 工具的学习与整理

    IBM jca 工具的学习与整理 背景 发现自己最早看到IBM这个工具的时间是 2022年9月份. 但是一直没有进行过仔细的学习与论证. 本周出现了一个问题. 虽然通过gclog明显看出来是一个oom ...

  5. Grub2 内核启动参数总结

    Grub2 内核启动参数总结 部分参数 biosdevname=0 net.ifnames=0 # 注意这个配置会修改网卡的名字, 比如之前是ens192 # 添加如上两个内容后就会变成 eth0 类 ...

  6. linux获取文件或者是进程精确时间的方法

    linux获取文件或者是进程精确时间的方法 背景 很多时候需要精确知道文件的具体时间. 也需要知道进程的开始的精确时间. 便于进行一些计算的处理. 其实linux里面有很多方式进行文件属性的查看. 这 ...

  7. [转帖]为非root用户添加NOPASSWD权限

    https://www.jianshu.com/p/d1e71bda4b34 查看树莓派默认是怎么为pi用户免去密码 所有配置文件都在 /etc 目录下,免去密码配置文件也不例外.在/etc/sudo ...

  8. [转帖]一文带你搞懂xxl-job(分布式任务调度平台)

    https://zhuanlan.zhihu.com/p/625060354 前言 本篇文章主要记录项目中遇到的 xxl-job 的实战,希望能通过这篇文章告诉读者们什么是 xxl-job 以及怎么使 ...

  9. [转帖]怎么查看Linux服务器硬件信息,这些命令告诉你

    https://zhuanlan.zhihu.com/p/144368206 Linux服务器配置文档找不到,你还在为查询Linux服务器硬件信息发愁吗?学会这些命令,让你轻松查看Linux服务器的C ...

  10. [转帖]15 个必须知道的 chrome 开发工具技巧

    在Web开发者中,Google Chrome是使用最广泛的浏览器.六周一次的发布周期和一套强大的不断扩大开发功能,使其成为了web开发者必备的工具.你可能已经熟悉了它的部分功能,如使用console和 ...