实现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 因类加载器隔离导致的数据共享问题。 未来可在此机制上扩展更多业务场景,如定制化质量工具、统一调用链追踪等。

相关推荐
赵得C37 分钟前
Java 多线程环境下的全局变量缓存实践指南
java·开发语言·后端·spring·缓存
打不过快跑1 小时前
YOLO 入门实战(二):用自定义数据训练你的第一个检测模型
人工智能·后端·python
敲代码的火锅1 小时前
基于pyroscope-go项目性能数据持续收集
后端·go
码事漫谈1 小时前
VS Code C#调试完全指南
后端
用户49055816081251 小时前
云原生系统如何实现“无状态会话”
后端
用户49055816081251 小时前
会话同步是数据面做,还是控制面做
后端
切糕师学AI1 小时前
浏览器访问 ASP.NET Core wwwroot 目录下静态资源的底层实现
后端·中间件·kestrel·管道·.net core web
bobz9651 小时前
HAMi-core 中 CUDA 劫持技术及 API 类型
后端
于顾而言2 小时前
【笔记】Linux高性能网络详解之DPDK
后端
二闹2 小时前
别再傻傻分不清!MyBatis两种分页方式到底用哪个?
后端·mysql