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

相关推荐
Postkarte不想说话16 分钟前
Jupyter Lab安装
后端
fliter18 分钟前
在 Async Rust 中实现请求合并(Request Coalescing)
后端
王立志_LEO19 分钟前
Gunicorn 启动django服务
后端
fliter20 分钟前
一个让我调试一周的 Rust match 陷阱
后端
一只大袋鼠31 分钟前
SpringBoot 初学阶段知识点汇总(一)
spring boot·笔记·后端
Rust研习社33 分钟前
Rust 官方拟定 LLM 政策,防止 LLM 污染开源社区?
开发语言·后端·ai·rust·开源
无风听海1 小时前
ASP.NET Core Minimal API 深度解析
后端·asp.net
IT_陈寒1 小时前
Java的finally块竟然不是你想的那个finally!
前端·人工智能·后端
zb200641201 小时前
Laravel4.x核心特性全解析
spring boot·后端·php·laravel