实现JVM-Sandbox-Repeater与Module间的数据共享

一、背景

我们的项目同时使用了 JVM-Sandbox-Repeater(用于流量录制回放)和自定义 JVM-Sandbox 模块(用于特定监控场景)。

由于 Repeater 和 Sandbox Module 遵循沙箱的模块隔离原则,它们运行在独立的类加载器中,无法直接共享数据。这导致了一些关键信息(如traceId)无法在两个组件间传递,限制了更复杂的监控场景实现。

本文将探讨如何突破这一限制,实现 JVM-Sandbox-Repeater 与 Sandbox Module 间的数据共享。

二、理解JVM-Sandbox Module

2.1 模块隔离的特性与挑战

JVM-Sandbox Module 是 JVM-Sandbox(沙箱)容器中实现具体功能逻辑的插件化单元。它利用 JVM-Sandbox 提供的动态字节码增强能力,让你能够以无侵入的方式,在目标 JVM 应用的运行期间,对其特定类的特定方法进行 AOP 拦截和增强。

关键特性:

  • 无侵入性:无需修改源码或重启服务
  • 动态插拔:模块可实时加载、卸载
  • 类隔离:模块间、模块与应用间类加载器隔离

隔离带来的挑战:

  • 模块A无法直接访问模块B的类
  • 模块无法直接访问业务应用中的自定义类
  • 默认情况下,模块间无法共享数据

2.2 模块应用场景

可以把 JVM-Sandbox Module 想象成一个功能强大的"监听器"或"拦截器"。它通过 JVM-Sandbox 容器"附着"到目标 JVM 进程上,监听你感兴趣的方法调用事件(例如方法执行前、返回后、抛出异常时),并在这些事件点执行你自定义的逻辑,比如记录日志、修改参数、模拟异常、统计耗时等。

  • 线上故障定位与诊断:类似 Arthas 的功能,例如动态跟踪方法调用参数、返回值、异常信息,排查性能瓶颈等。
  • 流量录制与回放(Repeater):录制线上用户的真实请求(入参和出参),用于线下回放测试、故障复现和诊断。阿里开源的 JVM-sandbox-repeater 正是基于此实现的。
  • 故障模拟与演练:模拟各种异常场景,如方法超时、抛出特定异常、返回错误结果等,以检验系统的容错性和稳定性。ChaosBlade 的 Java 场景实现就基于 JVM-Sandbox。官方自带的无敌破坏王插件:DebugRalphModule

三、数据共享方案

3.1 难点

  • 每个 Module 使用独立的 ModuleJarClassLoader → 类彼此隔离(即模块 A 无法直接访问模块 B 的类)。
  • 模块默认无法直接访问目标应用中的业务自定义类(如:com.xx.UserService),只能通过沙箱 API 提供的 Advice 对象,利用反射机制获取这些类的信息(如:类名、方法名、参数等)。

3.2 核心思路:全局共享

本次实现的目标是在 Repeater回放过程中,将 traceId 共享给 Sandbox Module 使用。

解决方案的核心是:通过 Bootstrap ClassLoader 加载共享类,创建全局数据,双方通过反射机制访问该类。

3.3 具体步骤

1、定义共享类

通过 mvn package 打包生成 JAR 文件,并将其放置于 JVM-sandbox 目录下

typescript 复制代码
public class ShareData {
​
    public static BlockingQueue<String> shareQueue = new LinkedBlockingQueue<>();
​
    public static void put(String data) {
        shareQueue.add(data);
    }
​
    public static void remove(String data) {
        shareQueue.remove(data);
    }
​
    public static void removeAll() {
        shareQueue.remove();
    }
​
    public static String get() throws InterruptedException {
        return shareQueue.take();
    }
}

2、全局加载共享类

修改 AgentLauncher 启动逻辑,确保共享类被 BootClassLoader 加载器加载,使其在全局范围内可访问

java 复制代码
com.alibaba.jvm.sandbox.agent.AgentLauncher@install
​
// 将Jar包注入到BootstrapClassLoader
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(
         "/opt/jvm-sandbox/sandbox/lib/ShareData.jar"
)));

3、Repeater 端写入数据

在回放过程中,通过反射将 traceId 写入共享区

typescript 复制代码
cn.zcygov.jvm.sandbox.repeater.plugin.core.impl.AbstractRepeater@repeat
​
static volatile Method putTraceMethod;
​
// 
public String putTrace(String traceId) {
        if (putTraceMethod == null) {
            synchronized (LOCK) {
                if (putTraceMethod == null) {
                    try {
                        // 反射调用
                        putTraceMethod = Class.forName("zcy.ShareData", false, null).getDeclaredMethod("put", String.class);
                        putTraceMethod.setAccessible(true);
                    } catch (Exception e) {
                        throw new RuntimeException("Failed to initialize traceMethod", e);
                    }
                }
            }
        }
​
        try {
            // 存储
            return (String) putTraceMethod.invoke(null,traceId);
        } catch (Exception e) {
            throw new RuntimeException("Trace invocation failed", e);
        }
}

4、Sandbox Module 端读取数据

在自定义模块中,通过反射从共享区读取数据

java 复制代码
com.alibaba.jvm.sandbox.module.debug.TestModule
​
static volatile Method getTraceMethod;
​
public String getTrace() {
        if (getTraceMethod == null) {
            synchronized (LOCK) {
                if (getTraceMethod == null) {
                    try {
                        getTraceMethod = Class.forName("zcy.ShareData", false, null).getDeclaredMethod("get");
                        getTraceMethod.setAccessible(true);
                    } catch (Exception e) {
                        throw new RuntimeException("Failed to initialize traceMethod", e);
                    }
                }
            }
        }
​
        try {
            return (String) getTraceMethod.invoke(null);
        } catch (Exception e) {
            throw new RuntimeException("Trace invocation failed", e);
        }
}

四、尾声

本文介绍了 基于共享类 + BootClassLoader 注入的数据共享机制,解决了 JVM-Sandbox 与 Repeater 因类加载器隔离导致的数据共享问题。 未来可在此机制上扩展更多业务场景,如定制化质量工具、统一调用链追踪等。

相关推荐
追逐时光者1 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_1 小时前
敏捷开发流程-精简版
前端·后端
苏打水com2 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧3 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧3 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧3 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧3 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧3 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng5 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6015 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring