整合JVM-SANDBOX与VMTOOL,实现支持OGNL的增强自定义MOCK

jvm-sandbox:基础了解及demo演示 阅读详情

背景

最近在公司开发基于JVM的“流量录制回放的工具”,简单理解是把生产环境用户的请求到响应包括过程中的含状态子组件,把整个过程入参和返回值录制下来,到测试环境一一回放Mock,达到测试接口子调用覆盖率,上线前不同代码版本运行情况对比等目的,提供更多信息帮助测试和开发同学提高系统健壮性。市面同行的类似方案

简单流程:

拦截JVM的方案,使用了阿里的 jvm-sandbox , 原理是attach(或agent形式启动)到运行中的JVM,利用Java提供的InstrumentationApi,加载自定义模块,关注对应的拦截点,重新编织字节码到运行中的程序里。

系统的其他部分,也引入了同样是阿里的Arthas Java工具,主要用到一些trace方法,反编译代码等功能。

问题

就工程复杂度来看,线上录制相对复杂度低,主要是系统间的调用,稳定性(过程中不影响正常业务),在运行一段时间考虑如何把流量逐一落盘存储即可。而到线下回放Mock的过程,则会遇到各种的问题,例如机器性能的差异, 组件不完整,网络问题等,其中最大的问题是,和录制环境状态的差异。

尽管目前工具拦截了的子调用组件(都是状态相关)已覆盖了日常大部分应用,但实际开发同学实施代码有各种不同的习惯与个例,回放时即时同样的代码,依然会导致有各种执行结果,例如:

  1. 子调用组件录制不全
  2. 直接使用本地缓存例如 ConcurrentHashMap等
  3. 线上录制的一些时间参数与回放时不一致
  4. 调用一些非状态相关的ABTest SDK导致走向不同的代码路径
  5. 直接代码里写死与环境绑定的逻辑
  6. 配置中心不一致

....

目前对回放结果的调优,参数/响应用了JsonPath的方式去忽略噪音,提高了命中率

例如:

// 入参例子
// 录制到时间是昨天, 回放时把第一个参数忽略不加入匹配
userMapper.selectListGreaterThanDateAndNicknameEq(new Date(), "jas");

// 响应例子
// 调用下游系统,例如个性化推荐,每次返回的结果长度不一样, 影响到下一个调用的入参或最终接口实际响应结果
otherRecommandService.getRecommandList(userId)

而其他通过忽略匹配规则没办法匹配到的情况,则使用需要使用自定义Mock的方式,去抹平系统环境状态差异

例如如下这种情况,只要在测试环境回放时把envService.isProductionmock成true:


if(envService.isProduction()) {
  // 执行正式环境代码...
} else {
  // 执行dev环境代码...
}


例如如下这种情况,如果能把getUsermock成返回指定数据库里的某个用户:

// 自实现缓存
private static Map<String, User> userCache = new HashMap<>();

private User getUser(String userId) {
    return userCache.get(userId);
}


如能达成以上的情况,则可大大方便测试的同学利用流量,在安全的测试环境里做各种链路的测试。在拦截方法方面,sandbox已可达到目的,而自定义Mock返回部分,结合到易上手与成熟度,本人则选择了OGNL去作为Mock返回值的方案,原因有下:

  1. 成熟的Apage项目, 在MyBatis等大库里均有使用 链接
  2. 上手非常简单,与系统的结合和验证都方便,所有语法两三分钟看完,对白盒测试的同学不成问题
  3. 返回值丰富, 基础类型/数组/Map/复杂JAVA对象/私有属性/静态变量等等等,都可以返回
  4. 过程运算支持变量,这可轻松地复用原有系统的基础逻辑

简单演示:

// 返回数组
Ognl.getValue("{1,3,4,5}")

// 返回Map
Ognl.getValue("#{\"name\": \"JAS\"}")

// map中带复杂对象变量
Ognl.getValue("#user=new User(),#{\"user\": #user}")

// 访问静态类静态
Ognl.getValue("@ContextFactory@traceId")


对于第4点,Ognl支持复用业务系统业务逻辑返回,我司大多业务系统都使用到Spring框架,熟悉Spring的朋友都知道,框架支持已注入的方式去获取ApplicationContext类的对象,只要获取到该对象,我们可以调用任意Bean的方法, 例如各种数据库mapper, redisTemplate, 各种业务Service 都不在话下。

// Spring原来获取ApplicationContext的方式, 通过Aop注入
@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }
}

但实际我们的视觉是在Sandbox的classLoader里,这和业务运行的classLoader可理解是一个平衡宇宙,即时在sandbox中拦截到例如某个service的方法,但我们也没办法去获取到实例化好的ApplicationContext对象。

// sandbox的注入代码示例
new EventWatchBuilder(moduleEventWatcher)
    .onClass("com.jasjojo.UserService") // 拦截类
    .onBehavior("getUser") //拦截方法
    .onWatch((event)-> {
        if(event.Type.equals(event.Before)) {
            event.getAdvice().getTarget() //获取到拦截的对象,但没办法从这个对象获得到SpringContext
        }
    })


下面有请决方案ArthasVmTool

vmtool 利用JVMTI接口,实现查询内存对象,强制 GC 等功能。

我们可以用官方例子attach一下正在执行的Spring容器, 遍可以获得对象

# 获取到执行中的ApplicationContext对象,后面也是ognl表达式
$ vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className org.springframework.context.ApplicationContext --express 'instances[0].getBeanDefinitionNames()'

这一原理是调用了JAVA提供的JVMTI接口,但调用该接口需要用到C或C++, 并且如果使用该接口, 则需要考虑跨平台编译(兼容Mac/Windows/Linux), 运行时增加了复杂性,通过查看VmTool的源码,见到C相关核心代码如下

// 对应同名nativejava方法
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_arthas_VmTool_getInstances0(JNIEnv *env, jclass thisClass, jclass klass, jint limit) {
    jlong tag = getTag();
    limitCounter.init(limit);
    jvmtiError error = jvmti->IterateOverInstancesOfClass(klass, JVMTI_HEAP_OBJECT_EITHER,
                                               HeapObjectCallback, &tag);
    if (error) {
        printf("ERROR: JVMTI IterateOverInstancesOfClass failed!%u\n", error);
        return NULL;
    }

    jint count = 0;
    jobject *instances;
    error = jvmti->GetObjectsWithTags(1, &tag, &count, &instances, NULL);
    if (error) {
        printf("ERROR: JVMTI GetObjectsWithTags failed!%u\n", error);
        return NULL;
    }

    jobjectArray array = env->NewObjectArray(count, klass, NULL);
    //添加元素到数组
    for (int i = 0; i < count; i++) {
        env->SetObjectArrayElement(array, i, instances[i]);
    }
    jvmti->Deallocate(reinterpret_cast<unsigned char *>(instances));
    return array;
}

简单理解是,只要在运行时,传入想要获取实例对象的Class,变回返回该Class在内存里所有已实例化的对象。自然我们在sandbox的拦截方法过程里,可以获取平衡宇宙业务classLoader里的Class引用,但更好的方法应该是sandbox-agent在初始化onLoad时,就尝试去vmtool获取有没有spinrg的ApplicationContext对象,这样可以提早作一些预判规则,并缓存起对象引用, 要达到这一点,我们只需要修改sandbox源码,让sandbox往下暴露Instrumentation对象即可。整体整合方案流程如下:

各部分核心代码如下:

// 修改Sandbox源码, 暴露Instrumentation
public interface ModuleEventWatcher {
    //...

    // 其他实现类都继承这个方法暴露Instrumentation对象
    Instrumentation getInst();
}

// 通过instrumentation获取所有class
public static Set<ClassLoader> getAllClassLoader(Instrumentation inst) {
    Set<ClassLoader> classLoaderSet = new HashSet<ClassLoader>();

    for (Class<?> clazz : inst.getAllLoadedClasses()) {
        ClassLoader classLoader = clazz.getClassLoader();
        if (classLoader != null) {
            classLoaderSet.add(classLoader);
        }
    }
    return classLoaderSet;
}

// VmTool通过Class去获取Spring ApplicationContext对象
// libPath是编译好对应当前系统的c++库位置, 可参考VmtoolCommand,后续工程打包部分详谈
public static synchronized VmTool getInstance(String libPath) {
    if (instance != null) {
        return instance;
    }

    if (libPath == null) {
        System.loadLibrary(JNI_LIBRARY_NAME);
    } else {
        System.load(libPath);
    }

    instance = new VmTool();
    return instance;
}
// 调用的native方法, 返回的范型就是对象列表
private static synchronized native <T> T[] getInstances0(Class<T> klass, int limit);

//Ognl获得Spring对象后的上下文设置,#spring就是一个可用的变量,指向ApplicationContext对象
ClassResolver resolver = new ClassLoaderClassResolver(targetClassLoader);
Map<String, Object> springMap = new HashMap();
springMap.put("spring", springContext); // #spring对象
OgnlContext ognlContext = new OgnlContext( new DefaultMemberAccess(true), resolver, new DefaultTypeConverter(), springMap);

// 如此便可调用到userMapper的查询方法
Object users = Ognl.getValue("#spring.getBean(\"userMapper\".selectList()[0])", ognlContext);

最后的工程部分一些细节:

  1. 我当前时间2022年8月拉sandbox master最新代码,修改好jvm-sandbox打包编译运行,可能会遇到java.lang.NoClassDefFoundError: Could not initialize class com.alibaba.jvm.sandbox.core.enhance.weaver.SingleEventFactory, 参考pr#Comment
  2. vmtool引用的c++库,可以用arthas编译好的版本, 也可以用gcc自己编译支持苹果arm cpu等
  3. 修改源码的方式,像这种非pr的改动,运行稳定后,最好提交到私有仓, 同时维持两个remote分支。未稳定经大量测试时,可build成jar暂时放到resources lib方式去引用
  4. ognl和vmtool会在比较底层去暴露许多系统的调用,只能用在测试环境!!!!!

最终大概效果:

通过iMock学习Jvmsandbox jvm-sandbox学习记录 阅读详情

相关推荐

JVM-Sandbox--测试技术中引流回放的应用

这样,在Sandbox中就可以调用新的类和方法,从而实现Mock的功能。、故障模拟、动态日志、行链路获取等等工具,就算我开发完成了,这些工具底层实现原理相同,同时使用,要怎么消除这些工具之间的影响,怎么保证这些工具动态加载,怎么保证动态加载/卸载之后不会影响其他工具,怎么保证在工具有问题的时候,快速消除影响,代码还原。系统间的异常模拟可以使用的工具很多,可是系统内的异常模拟怎么办,加开关或是用AOP在开发系统中实现,好想开发一个更优雅的异常模拟工具,既能模拟系统间的异常,又能模拟系统内的异常。

Gamble6 1969

jvm-sanbox详解-agent方式和attach方式

Instrumentation包的简介 JDK从1.5开始,在rt.jar包下有了java.lang.instrument这个文件夹,刚开始只支持agent方式启动,但使用还是不太方便,因为我们在JDK1.6后增加了attach方式。在官方文档提供了两种方式 agent命令方式 在jvm启动参数中增加 -javaagent:jarpath[=options] agent有两点要求: agent jar必须包含Premain-Class属性,值是agent class启动类 必须实现 一个 public s

xyznol的博客 2442

【雷达信号处理实战】从原理到代码:脉冲多普勒(PD)处理全流程解析与MATLAB仿真

本文深入解析了脉冲多普勒(PD)雷达信号处理的核心原理与全流程。通过剖析多普勒效应、脉冲压缩与慢时间FFT等关键技术,并结合详细的MATLAB仿真代码,手把手指导如何从信号建模、回波模拟到最终生成距离-多普勒谱,实现运动目标的精确测距与测速,直观展示了相参积累带来的显著性能增益。

pca5navigator的博客 954

Jvm sandbox mock机制实践

Jvm sandbox沙箱机制,是一种实现不重启、无侵入改变目标应用返回值的面向切面编程解决方案。测试方面来说,对于RPC接口、HTTP接口都适用。

程序员小杨哥的博客 1642

拆解博世、大陆的EMB方案:自增力机构如何省下83%的能耗?

本文深度解析了博世和大陆的电子机械制动(EMB)技术,重点探讨了自增力机构如何实现83%的能耗节省。通过对比电机直驱型和自增力型EMB的技术参数,揭示了自增力机构在能量优化和系统集成方面的创新优势,为汽车电动化和智能化提供了关键技术支持。

weixin_42665725的博客 394

Windows摄像头错误0xA00F4240根因与注册表修复指南

Windows摄像头错误码0xA00F4240常被误判为硬件故障,实则源于UWP应用模型下权限调用链断裂——核心是COM组件调用失败(HRESULT 0x80004003),本质反映Windows Runtime API对注册表策略键值(如CapabilityAccessManager\ConsentStore\webcam)的强依赖。该机制涉及服务状态、DWORD权限配置、ACL继承控制等多层校验,任一环节异常(如ValueLastModified时间戳损坏或ALL APPLICATION PACKAGE

weixin_31742871的博客 386

JVM Sandbox入门详解

在日常开发中,经常会接触到面向AOP编程的思想,我们通常会使用Spring AOP来做统一的权限认证、异常捕获返回、日志记录等工作。之所以使用Spring AOP来实现上述功能,是因为这些场景本质上来说都是与业务场景挂钩的,但是具有一定的抽象程度,并且绝大多数业务逻辑类都已经被Spring容器托管了。

听得到微笑的博客 1万+

告别手动复制粘贴!用Python脚本+pywinauto实现PC微信消息自动发送(附完整源码)

本文详细介绍了如何利用Python和pywinauto实现PC微信消息的自动化发送,告别繁琐的手动操作。从环境搭建、UI控件定位到构建健壮的发送流程,再到高级功能扩展和项目打包部署,全面解析了自动化办公的实现方案,显著提升工作效率。

weixin_30315723的博客 585

基于jvm-sandbox的imock开发指南

imock 包含mock-module、mock-web ,mock-module就是jvm-sandbox的模块,需要安装到目标应用服务器,mock-web 为mock服务后台,imock是前后端分离,还有一个前端react 项目 imcok-web。Dubbo 使用代理对象来实现远程服务的调用,代理对象是在运行时动态生成的,而真正的目标对象是通过 Dubbo 的远程调用机制获取的。团队今年的指标是为公司提供一个方法级的mock平台, 这个重要的任务落在了我的身上。

李梨同学的博客 1264

在LinuxMint 22.1(Ubuntu24.04)上安装使用同花顺远航版

Linux用户成功在LinuxMint22.1系统上通过Wine9.0运行同花顺远航版,解决了长期困扰的股票行情软件问题。此前Linux原生版同花顺功能简陋且停止更新,Wine版通达信存在显示问题。作者作为自由软件支持者,通过调整Wine配置文件解决了高分屏显示异常问题,实现了远航版的正常使用。期待同花顺能重启Linux原生版开发,支持更多处理器架构,完善国产软件生态。该方案为Linux股票软件用户提供了实用参考。

5135

ABB变频器使用PROFINET IO通信模块时的激活方法

ABB变频器使用PROFINET IO通信模块时的激活方法

Robot_PLC_HMI_Automation 2734

JVM-SANDBOX:从阿里精准测试走出的开源贡献奖

阿里妹导读:稳定性是历年双11的技术质量保障核心。从 2016 年开始淘宝技术质量部潜心修行,创新地研发了一套实时无侵入的字节码增强框架,于是「JVM-SANDBOX」诞...

阿里技术 1146

超详细教程YoloV8官方推荐免费数据集网站Roboflow一键导出Voc、COCO、Yolo、Csv等格式

YoloV8官方推荐了一个数据集网站,就是Roboflow。Roboflow是一个数据集管理平台,提供了免费的数据集,同时也支持上传自己的数据集进行格式转换。使用Roboflow,开发者可以方便地获取所需格式的数据集,无需手动转换格式。此外,Roboflow还提供了多种数据预处理、数据增强等功能,可帮助开发者更好地优化训练数据

Snu77的博客 2万+

Alibaba之jvm-sandbox初体验

前言 在开始之前,我们先来模拟一下以下的场景: 小李:“小明,你的接口没有返回数据,麻烦帮忙看一下?” 小明:“我这边的数据也是从别人的服务器中拿到的,但是我不确定是因为逻辑处理有问题导致没有结果,还是因为我依赖的服务有问题而没有返回结果,我需要确认一下。” 小明:“哎呀,线上没有日志,我需要加个日志上个线。” 30 分钟之后…… 小明:“不好意思,日志加错地方了……稍等……” 接来下隆重登场的就...

山间浓雾有路灯的博客 1万+

python批量调用大模型API:多线程和异步协程

本文介绍了利用多线程技术批量调用大模型API的实现方法。核心是通过并发请求提高处理效率,主要采用线程池管理并发数量、结果聚合等技术。文章提供了一个完整的Python实现示例,包含APIBatchCaller类及其single_api_call和batch_call方法,详细解析了并发控制、错误处理等关键点,并给出了使用注意事项,包括并发数设置、重试策略和超时处理等。该方案特别适合IO密集型任务,能显著提升批量API调用的效率。

Cachel Wood的博客 759

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

jvm-sandbox-repeater时间mock插件设计与实现

我们拦截了org.springframework.web.method.support.InvocableHandlerMethod#doInvoke的入口,在执行之前repeatContext.setCanMockDate(true),执行之后 repeatContext.setCanMockDate(false);我自己经过多次测试,下面的实现方式是能够有效拦截并生效的,因此sandbox是有能力拦截java native实现的, 基于此,怎么实现就简单了;

Viogs的专栏 1245

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

探索阿里巴巴的JVM SandBox:一款动态Java代码安全运行环境

探索阿里巴巴的JVM SandBox:一款动态Java代码安全运行环境 在软件开发中,有时我们需要在一个安全的环境中执行不信任或未知来源的代码,以防止潜在的安全风险和性能问题。阿里巴巴的正是为此目的而设计的一个强大工具。它允许你在Java虚拟机(JVM)上安全地运行任意代码,通过限制其访问资源的能力,确保了系统的稳定性和安全性。 项目简介 JVM SandBox是基于Java的沙箱框架,它为开发者...

gitblog_00056的博客 659

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Sandbox 入门(打包、安装、启动、调试、日志)

JVM-SANDBOX(沙箱)实现了一种在不重启、不侵入目标JVM应用的AOP解决方案。 SANDBOX 入门(打包、安装、启动、调试)

xiaolyuh的专栏 5157

jvm-sandbox实战之windows调试

由于jvm-sandbox目前只提供了shell脚本来进行运行安装部署,对于mac电脑比较方便,但是windows的方式的话比较痛苦,走了很多弯路,慢慢深入了解之后发现windows的方式也是可以去做的。 1. 下载安装包 细看sandbox提供的一些脚本可以发现,其实脚本里的内容就是想把本地的项目结构打包成: ├── bin │ ├── attach-pid.sh │ ├── sandbox.sh │ └── z-demo.sh ├── cfg │ ├── repeater-config.

长沙要起风了、 1887

java诊断工具-Arthas(vmtool命令)查看spring中的对象及属性

1、查看spring所管理的所有的bean名称 vmtool --action getInstances --className org.springframework.context.ApplicationContext --express 'instances[0].getBeanDefinitionNames()' 2、查看具体的某个bean及其属性 vmtool --action getInstances --className org.springframework.context.Ap

qaws9403的博客 1万+

8年后端,JAVA神器之 Arthas

8年后端,JAVA神器之 Arthas

jourey_for_you的博客 1748

arthas java内存工具使用

arthas工具排查问题常用命令。watch,jad,vmtool,getstatic,查看变量的值,反编译java源代码

qq_42602993的博客 3658

java api 获取jvm实例_JVMTI那些事——和Java相互调用

前面几篇文章介绍了JVMTI接口的一些基本概念,以及如何编写一个基于JVMTI的agent。那些简单的例子只是JVMTI agent自己实现一些简单的功能,如果能够将JVMTI提供的接口经过包装之后提供给Java使用,能够发挥更大的作用。需求本文源自一个实际的需求:业务代码需要在不依赖spring容器的情况下,或者spring的ApplicationContext对象实例,进而能够获取到sprin...

weixin_42174176的博客 691

Java 诊断利器 Arthas JVM命令

jvm 相关命令

前尘忆梦Memory的博客 3318

Java代码审计手册(2)

此文为翻译文章(可能会出现错误),由于原地址被打入xss,所以保存留记录 目录 潜在的XPath注入(XPATH_INJECTION) 找到Struts 1端点(STRUTS1_ENDPOINT) 找到Struts 2端点(STRUTS2_ENDPOINT) 找到了Spring端点(SPRING_ENDPOINT) 禁用Spring CSRF保护(SPRING_CSRF_PROTECTION_DISABLED) Spring CSRF无限制请求映射(SPRING_CSRF_UNRESTRICTED_RE.

kudos123的博客 2575
上一篇: Apache HttpClient 5 使用详细教程
下一篇: 背废完虐面试官!字节架构师8年心血终成《图解设计模式》手册
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值