【性能测试】ChaosTesting(混沌测试)ChaosBlade(混沌实验工具)(五)-jvm混沌实验

server/2024/11/24 6:00:25/

6. chaosblade-jvm实验场景

6.1 挂载 java agent

        blade prepare jvm

6.1.1 介绍

挂载 java agent,执行 java 实验场景必要步骤

6.1.2 参数

-j, --javaHome string: 指定 JAVA_HOME 路径,用于指定 java bin 和 tools.jar,如果不添加此参数,默认会优先获取 JAVA_HOME 环境变量,如果获取失败,会解析指定进程参数获取 JAVA_HOME,获取失败,会使用 chaosblade 自带的 tools.jar
–pid string: java 进程ID
-P, --port int: java agent 暴露服务的本地端口,用于下发实验命令
-p, --process string: java 进程关键词,用于定位 java 进程
-d, --debug: 开启 debug 模式

6.1.3 案例

# 指定 pid 执行 java agent 挂载
blade prepare jvm --pid 26652
# 命令也可简写为
blade p jvm --pid 26652

执行成功,会返回实验准备的 UID,例如:

{"code":200,"success":true,"result":"2552c05c6066dde5"}

2552c05c6066dde5 就是实验准备对象的 UID,执行卸载操作需要用到此 UID,例如

blade revoke 2552c05c6066dde5
# 命令也可简写为
blade r 2552c05c6066dde5

如果 UID 忘记,可通过以下命令查询

blade status --type prepare --target jvm
# 命令也可简写为:
blade s --type p --target jvm

挂载 java agent 操作是个比较耗时的过程,在未返回结果前请耐心等待

6.0 创建jvm

        blade create jvm

6.0.1 介绍

jvm 本身相关场景,以及可以指定类,方法注入延迟、返回值、异常故障场景,也可以编写 groovy 和 java 脚本来实现复杂的场景。目前支持的场景如下

  1. [blade create jvm CodeCacheFilling](blade create jvm CodeCacheFilling.md) 填充 jvm code cache
  2. [blade create jvm OutOfMemoryError](blade create jvm OutOfMemoryError.md) 内存溢出,支持堆、栈、metaspace 区溢出
  3. [blade create jvm cpufullload](blade create jvm cpufullload.md) java 进程 CPU 使用率满载
  4. [blade create jvm delay](blade create jvm delay.md) 方法延迟
  5. [blade create jvm return](blade create jvm return.md) 指定返回值
  6. [blade create jvm script](blade create jvm script.md) 编写 groovy 和 java 实现场景
  7. [blade create jvm throwCustomException](blade create jvm throwCustomException.md) 抛自定义异常场景

6.0.2 参数

此处列举 jvm 支持的通用参数:
–pid string: 指定 java 进程号
–process string: 指定 java 进程名,如果同时填写
–timeout string:
设定运行时长,单位是秒,通用参数
JVM 方法级别的故障场景通用参数:
–classname string: 指定类名,必须是实现类,带全包名,例如 com.xxx.xxx.XController (必填项)
–methodname string: 指定方法名,注意相同方法名的方法都会被注入相同故障 (必填项)
–after: 方法执行完成返回前注入故障,比如修改复杂的返回对象
–effect-count string: 限制影响数量
–effect-percent string: 限制影响百分比
各场景还有自身所独有的参数,可以在每个场景文档中查看

6.0.3 案例

此处举个简单的例子:当前 Java 进程 CPU 使用率满载

# 先执行 prepare 操作
blade prepare jvm --process tomcat
{"code":200,"success":true,"result":"af9ec083eaf32e26"}# 执行进程内 CPU 满载
blade create jvm cpufullload --process tomcat
{"code":200,"success":true,"result":"2a97b8c2fe9d7c01"}
# 验证结果:见下图
-w461
Copy
# 停止实验
blade destroy 2a97b8c2fe9d7c01# 卸载 agent
blade revoke af9ec083eaf32e26

在这里插入图片描述

6.2 指定类方法调用延迟

        blade create jvm delay

6.2.1 参数

以下是此场景特有参数,通用参数详见:[blade create jvm](blade create jvm.md)
–effect-count string: 影响的请求条数
–effect-percent string: 影响的请求百分比
–time string: 延迟时间,单位是毫秒,必填项
–offset string: 延迟时间上下偏移量,比如 --time 3000 --offset 1000,则延迟时间范围是 2000-4000 毫秒

6.2.2 案例

业务方法通过 future 获取返回值,代码如下:

@RequestMapping(value = "async")
@ResponseBody
public String asyncHello(final String name, long timeout) {if (timeout == 0) {timeout = 3000;}try {FutureTask futureTask = new FutureTask(new Callable() {@Overridepublic Object call() throws Exception {return sayHello(name);}});new Thread(futureTask).start();return (String)futureTask.get(timeout, TimeUnit.MILLISECONDS);} catch (TimeoutException e) {return "timeout, " + e.getMessage() + "\n";} catch (Exception e) {return e.getMessage() + "\n";}
}

我们对 sayHello 方法调用注入 4 秒延迟故障,futureTask.get(2000, TimeUnit.MILLISECONDS) 会发生超时返回:

blade c jvm delay --time 4000 --classname=com.example.controller.DubboController --methodname=sayHello --process tomcat
{"code":200,"success":true,"result":"d6ebea0dc28b6ab3"}

注入故障前:
在这里插入图片描述

注入故障后:
在这里插入图片描述

停止实验:

blade d d6ebea0dc28b6ab3

6.3 指定类方法的返回值

        blade create jvm return

6.3.1 介绍

指定类方法的返回值,仅支持基本类型、null 和 String 类型的返回值。

6.3.2 参数

以下是此场景特有参数,通用参数详见:[blade create jvm](blade create jvm.md)
–effect-count string: 影响的请求条数
–effect-percent string: 影响的请求百分比
–value string: 返回指定值,仅支持基本类型和字符串类型,如果想返回 null,可以设置为 --value null 。必选项

6.3.3 案例

指定com.example.controller.DubboController类,下面业务方法返回 “hello-chaosblade”

@RequestMapping(value = "hello")
@ResponseBody
public String hello(String name, int code) {if (name == null) {name = "friend";}StringBuilder result = null;try {result = new StringBuilder(sayHello(name));} catch (Exception e) {return e.getMessage() + "\n";}return result.toString() + "\n";
}

故障注入命令如下:

blade c jvm return --value hello-chaosblade --classname com.example.controller.DubboController --methodname hello --process tomcat

故障注入之前:
在这里插入图片描述
故障注入之后:
在这里插入图片描述
停止实验:

blade d d31e24dea782a275

上述代码调用 sayHello 方法,我们对 sayHello 方法注入返回 null 故障,sayHello 方法如下:

private String sayHello(String name) throws BeansException {demoService = (DemoService)SpringContextUtil.getBean("demoService");StringBuilder result = new StringBuilder();result.append(demoService.sayHello(name));return result.toString();
}

执行以下命令:

blade c jvm return --value null --classname com.example.controller.DubboController --methodname sayHello --process tomcat

故障注入之后:
在这里插入图片描述

6.4 编写 java 或者 groovy 脚本实现复杂的故障场景

        ** blade create jvm script**

6.4.1 介绍

编写 java 或者 groovy 脚本实现复杂的故障场景,比如篡改参数、修改返回值、抛自定义异常等

6.4.2 参数

以下是此场景特有参数,通用参数详见:[blade create jvm](blade create jvm.md)
–effect-count string: 影响的请求条数
–effect-percent string: 影响的请求百分比
–script-content string: 脚本内容,是 Base64 编码后的内容,相关工具类 Base64Util。注意,不能和 script-file 同时使用。
–script-file string: 脚本文件,文件绝对路径
–script-name string: 脚本名称,日志记录用,可不填写。
–script-type string: 脚本类型,取值为 java 或 groovy,默认为 java。
使用 script-content 指定演练脚本内容,不添加 script-type 参数,默认为 java 脚本,将调用 java 引擎解析器。

blade c jvm script --classname com.example.controller.DubboController --methodname call --script-content aW1wb3J0IGphdmEudXRpbC5NYXA7CgppbXBvcnQgY29tLmV4YW1wbGUuY29udHJvbGxlci5DdXN0b21FeGNlcHRpb247CgovKioKICogQGF1dGhvciBDaGFuZ2p1biBYaWFvCiAqLwpwdWJsaWMgY2xhc3MgRXhjZXB0aW9uU2NyaXB0IHsKICAgIHB1YmxpYyBPYmplY3QgcnVuKE1hcDxTdHJpbmcsIE9iamVjdD4gcGFyYW1zKSB0aHJvd3MgQ3VzdG9tRXhjZXB0aW9uIHsKICAgICAgICBwYXJhbXMucHV0KCIxIiwgMTExTCk7CiAgICAgICAgLy9yZXR1cm4gIk1vY2sgVmFsdWUiOwogICAgICAgIC8vdGhyb3cgbmV3IEN1c3RvbUV4Y2VwdGlvbigiaGVsbG8iKTsKICAgICAgICByZXR1cm4gbnVsbDsKICAgIH0KfQo=  --script-name exception

使用 script-file 参数指定文件演练:

blade c jvm script --classname com.example.controller.DubboController --methodname call --script-file /Users/Shared/IdeaProjects/Workspace_WebApp/dubbodemo/src/main/java/com/example/controller/ExceptionScript.java --script-name exception

执行 groovy 脚本实验场景,参数同上,但必须添加 --script-type groovy 参数。如

blade c jvm script --classname com.example.controller.DubboController --methodname call --script-file /Users/Shared/IdeaProjects/Workspace_WebApp/dubbodemo/src/main/java/com/example/controller/GroovyScript.groovy --script-name exception --script-type groovy 

脚本规范
必须创建一个类,对类名和包名没有要求,其中所依赖的类,必须是目标应用所具备的类。

同包下的类引用,必须写全包名,比如故障脚本类是 com.example.controller.ExceptionScript,类中引入了同包下的 DubboController 类,则 DubboController 必须添加 com.example.controller.DubboController。引入非同包下的类,无需写全包名。

必须添加 public Object run(Map<String, Object> params) 方法,其中 params 对象中包含目标方法参数,key 是参数索引下标,从 0 开始,比如目标方法是 public String call(Object obj1, Object obj2){},则 params.get(“0”)则返回的是 obj1 对象,可以执行params.put(“0”, ) 来修改目标方法参数(目标方法及 --classname 和 --methodname 所指定的类方法)。

上述方法返回的对象如果不为空,则会根据脚本中返回的对象来修改目标方法返回值,注意类型必须和目标方法返回值一致。如果上述方法返回 null,则不会修改目标方法返回值。

6.4.3 案例

对以下业务类做修改返回值实验场景:

@RestController
@RequestMapping("/pet")
public class PetController {@GetMapping("/list")public Result<List<PetVO>> getPets() {Map<Long, Discount> petDiscount = discountManager.getPetDiscounts().stream().filter(discount -> discount.getExpired() == 0).collect(Collectors.toMap(Discount::getPetId,Function.identity()));List<PetVO> pets = petManager.getPets().stream().map(pet -> {PetVO petVO = PetVO.from(pet);Discount discount = petDiscount.get(pet.getId());if (null != discount && null != discount.getDiscountPrice() && discount.getDiscountPrice() > 0L) {petVO.setDiscountPrice(discount.getDiscountPrice());}return petVO;}).collect(Collectors.toList());return Result.success(pets);}

则编写 Java 脚本,实现对 getPets 方法做返回值修改:

package com.alibaba.csp.monkeyking.controller;import java.util.ArrayList;
import java.util.List;
import java.util.Map;import com.alibaba.csp.monkeyking.demo.model.Pet;
import com.alibaba.csp.monkeyking.model.PetVO;
import com.alibaba.csp.monkeyking.model.Result;public class ChaosController {public Object run(Map<String, Object> params) {ArrayList<PetVO> petVOS = new ArrayList<>();for (int i = 0; i < 3; i++) {Pet pet = new Pet();pet.setName("test_" + i);PetVO petVO = PetVO.from(pet);petVOS.add(petVO);}Result<List<PetVO>> results = Result.success(petVOS);return results;}
}

保存文件后,通过上面 使用方式 部分的命令来调用,也可以将其进行 Base64 编码,通过指定 script-content 参数来指定编码后的内容。

blade c jvm script --classname com.alibaba.csp.monkeyking.controller.PetController --methodname getPets --script-file /Users/Shared/IdeaProjects/Workspace_WebApp/dubbodemo/src/main/java/com/alibaba/csp/monkeyking/controller/ChaosController --script-name specifyReturnObj

未执行实验之前页面:
在这里插入图片描述
执行实验之后:
在这里插入图片描述

6.5 指定 java 进程 CPU 满载

        blade create jvm cpufullload

6.5.1 介绍

指定 java 进程 CPU 满载,可以简写为 blade c jvm cfl

6.5.2 参数

以下是此场景特有参数,通用参数详见:[blade create jvm](blade create jvm.md)
–cpu-count string: 绑定的 CPU 核数,即指定几个核满载

6.5.3 案例

指定全部核满载

blade c jvm cfl --process tomcat 
{"code":200,"success":true,"result":"48d70f01e65f68f7"}

查看该进程 CPU 使用率:
-w454
停止实验:

blade d 48d70f01e65f68f7

指定两个核满载(测试机器是 8 个核)

blade c jvm cfl --cpu-count 2 --process tomcat
{"code":200,"success":true,"result":"a929157644688b15"}

查看进程 CPU 使用率是满核的四分之一:
在这里插入图片描述

6.6 内存溢出场景

        blade create jvm OutOfMemoryError

6.6.1 介绍

内存溢出场景,命令可以简写为:blade c jvm oom

6.6.2 参数

以下是此场景特有参数,通用参数详见:[blade create jvm](blade create jvm.md)
–area string: JVM 内存区,目前支持 [HEAP, NOHEAP, OFFHEAP],必填项。用Heap来表示Eden+Old,,用NOHEAP来表示metaspace,用OFFHEAP来表示堆外内存
–block string: 指定对象大小,仅支持 HEAP 和 OFFHEAP 区,单位是 MB
–interval string: 单位ms,默认500两次oom异常间的时间间隔,只有在非暴力模式才生效,可以减缓gc的频率,不用担心进程会无响应
–wild-mode string: 默认false,是否开启暴力模式,如果是暴力模式,在OOM发生之后也不会释放之前创建的内存,可能会引起应用进程无响应

6.6.3 案例

堆内存占用:

blade c jvm oom --area HEAP --wild-mode true --process tomcat{"code":200,"success":true,"result":"99b9228b9632e043"}

故障注入之前: 在这里插入图片描述
故障注入之后:在这里插入图片描述

停止 HEAP 内存占用:

blade d 99b9228b9632e043

创建 Metaspace 区内存占用,注意,执行完此场景后,需要重启应用!!!!:

blade c jvm oom --area NOHEAP --wild-mode true --process tomcat{"code":200,"success":true,"result":"93264dd07149cf54"}

故障注入后:在这里插入图片描述

6.6.4 实现原理

根据不同区注入
java.lang.OutOfMemoryError: Java heap space
创建 Heap的话分为Young,Old,这块区域的oom是最好重现,只需要不断的创建对象就可以,如果内存使用达到了 Xmx或者Xmn所规定的大小,并且gc回收不了,就会触发oom错误。

检查 • 可以通过 jmap -heap pid 来查看当前堆占用情况是否到了100% • 可以通过jstat -gcutil pid 来查看是否发生了gc,因为会一直创建新的对象,所以会频繁触发gc操作

恢复 当演练终止后,会停止产生新的对象,但此时不一定heap就恢复了,因为恢复需要触发gc才可以进行回收,当然也可以通过手动调用 System.gc()来强行触发gc,但是如果你的启动参数里面有 -XX:+DisableExplicitGC 那么这个命令就无法生效了.

注意 触发OOM的时候可能会导致进程被操作系统所kill,这个原因是因为你的Xmx设置的不合理,比如操作系统内存只有3G,但是你Xmx会设置了3G甚至更多,那么就会因为系统内存不足,而被os kill掉进程,所以这里务必要注意Xmx大小

java.lang.OutOfMemoryError: Metaspace
创建 Metaspace可以通过不断的加载类对象来创建,当大小超过了 -XX:MaxMetaspaceSize 并且无法进行gc回收就会抛出 oom错误了

检查 • 可以通过jstat -gcutil pid 来查看 M区的使用情况以及gc的次数

恢复 类对象的回收条件在jvm里面比较苛刻,需要满足很多条件,就算满足了条件,触发gc了也不一定回收,只要有下面任何一个条件就无法被回收. • objects of that class are still reachable. • the Class object representing the class is still reachable • the ClassLoader that loaded the class is still reachable • other classes loaded by the ClassLoader are still reachable 因此最好的办法就是重启应用.

java.lang.OutOfMemoryError: Direct buffer memoryDirectBuffer
在这里插入图片描述
创建 堆外内存可以直接通过ByteBuffer.allocateDirect 来产生,并且会一直消耗系统内存.

检查 • 因为堆外内存不属于堆里面,所以你通过jmap命令很难发现,但是可以通过 jstat -gcutil pid 来查看,如果频发出发了fullgc,但是e,O,M区都没发生变化, 那就是进行堆外内存回收 • 可以通过free -m 查看内存使用情况

注意 同样,如果没有设置最大堆外内存大小,同样会因为OS的memory耗尽而导致进程被杀,所以需要配置比如下面的参数: -XX:MaxDirectMemorySize=100M

6.7 调用频率比较高的代码

        blade create jvm CodeCacheFilling

6.7.1 介绍

CodeCache主要用于存放native code,其中主要是JIT编译后的代码。被JIT编译的一般都是“热代码”,简单说就是调用频率比较高的代码,JIT编译后,代码的执行效率会变高,CodeCache满会导致JVM关闭JIT编译且不可再开启,那么CodeCache满会引起系统运行效率降低,导致系统最大负载下降,当系统流量较大时,可表现为RT增高、QPS下降等。 命令可以简写为:blade c jvm ccf

6.7.2 参数

此场景无特有参数,通用参数详见:[blade create jvm](blade create jvm.md)

6.7.3 案例

6.7.3.1 注入 CodeCache 满故障:
blade c jvm CodeCacheFilling --process tomcat                                                                          {"code":200,"success":true,"result":"f0e896f38c704894"}

在这里插入图片描述

6.7.3.2 实现原理

由于CodeCache主要存放JIT编译的结果,所以填充CodeCache分为两步,第一步是生成用于触发JIT编译的class,方式是通过动态编译生成大量的class;第二步是编译后生成的class进行实例化和频繁调用(“加热”),直到触发JIT编译后进入CodeCache区。通过这样方式不停的填充CodeCache,直到JIT编译关闭

6.7.3.3 常见问题
  1. 由于需要编译和“加热”代码,所以在填充的过程中CPU占用率会很高;并且会持续一段时间(测试中,默认大小的情况下,从无占用到填充满约5分钟,实际情况下,CodeCache都会有一定的使用率,所以时间不会那么长);
  2. 由于“加热”过程中需要实例化大量的class,会有大量对象一直无法被GC回收,有概率导致Metaspace满而产生OOM;
  3. 由于无法直接判断JIT编译是否关闭,所以只能根据CodeCache占用量来判断,但是JIT编译关闭时,CodeCache占用量的阈值并不能精准获取,所以是通过CodeCache的增长来判断的,如果5秒内CodeCache占用量都无变化,即判断JIT编译关闭(JIT编译关闭后,CodeCache占用量不再变化);
  4. 目前是根据CodeCache的默认大小来设计的(生成class数量等),即240M(jdk8 64bit),如果设置更大的CodeCache(-XX:ReservedCodeCacheSize)的话,持续时间会更长,甚至由于动态产生的class数量不够而导致无法填充满;
  5. 由于JIT编译关闭后不可再手工开启,所以该故障无法直接恢复,需要用户手工重启应用系统来恢复;

6.8 指定类方法抛自定义异常

        blade create jvm throwCustomException

6.8.1 介绍

指定类方法抛自定义异常,命令可以简写为 blade c jvm tce

6.8.2 参数

以下是此场景特有参数,通用参数详见:[blade create jvm](blade create jvm.md)
–effect-count string: 影响的请求条数
–effect-percent string: 影响的请求百分比
–exception string: 异常类,带全包名,必须继承 java.lang.Exception 或 java.lang.Exception 本身
–exception-message string: 指定异常类信息,默认值是 chaosblade-mock-exception

6.8.3 案例

类名:com.example.controller.DubboController,业务代码如下:

private String sayHello(String name) throws BeansException {demoService = (DemoService)SpringContextUtil.getBean("demoService");StringBuilder result = new StringBuilder();result.append(demoService.sayHello(name));return result.toString();
}

指定以上方法抛出 java.lang.Exception 异常,影响两条请求,命令如下

blade c jvm throwCustomException --exception java.lang.Exception --classname com.example.controller.DubboController --methodname sayHello --process tomcat --effect-count 2{"code":200,"success":true,"result":"3abbe6fe97d6bc75"}

验证结果:
注入前: 在这里插入图片描述
注入后: 在这里插入图片描述
第三次请求后恢复正常: 在这里插入图片描述

停止实验:

blade d 3abbe6fe97d6bc75

http://www.ppmy.cn/server/20871.html

相关文章

Unity打开Android文件管理器并加载文件

1、在AssetStore商店中加入免费插件 2、调用代码 3、使用UnityWebRequest加载路径数据

实时采集麦克风并播放(springboot+webscoekt+webrtc)

项目技术 springbootwebscoektwebrtc 项目介绍 项目通过前端webrtc采集麦克风声音&#xff0c;通过websocket发送后台&#xff0c;然后处理成g711-alaw字节数据发生给广播UDP并播放。 后台处理项目使用线程池(5个线程)接受webrtc数据并处理g711-alaw字节数组放到Map容器中&…

Pytorch常用的函数(八)常见优化器SGD,Adagrad,RMSprop,Adam,AdamW总结

Pytorch常用的函数(八)常见优化器SGD,Adagrad,RMSprop,Adam,AdamW总结 在深度学习中&#xff0c;优化器的目标是通过调整模型的参数&#xff0c;最小化&#xff08;或最大化&#xff09;一个损失函数。 优化器使用梯度下降等迭代方法来更新模型的参数&#xff0c;以使损失函数…

【Redis 开发】Redis持久化(RDB和AOF)

Redis持久化 RDBAOFRDB和AOF的区别 RDB RDB全称Redis DataBase Backup file &#xff08;Redis数据备份文件&#xff09;&#xff0c;也被称为Redis数据快照&#xff0c;简单来说就是把内存中的所有数据都记录到磁盘中&#xff0c;当Redis实例故障重启后&#xff0c;从磁盘读取…

node.js如何实现留言板功能?

一、实现效果如下&#xff1a; 20240422_160404 二、前提配置&#xff1a; 配置&#xff1a;需要安装并且导入underscore模板引擎 安装&#xff1a;在控制台输入npm install underscore -save 文件目录配置&#xff1a; 1》在文件里建一个data文件夹&#xff0c;此文件夹下…

51.HarmonyOS鸿蒙系统 App(ArkUI)通知

普通文本通知测试 长文本通知测试 多行文本通知测试 图片通知测试 进度条通知测试 通知简介 应用可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知常见的使用场景&#xff1a; 显示接收到的短消息、…

jupyter notebook设置代码自动补全

jupyter notebook设置代码自动补全 Anaconda Prompt窗口执行 pip install jupyter_contrib_nbextensionsjupyter contrib nbextensions install --userpip install jupyter_nbextensions_configuratorjupyter nbextensions_configurator enable --user按如下图片设置 卸载jed…

字符函数·字符串函数·C语言内存函数—使用和模拟实现

字符函数字符串函数C语言内存函数 1.字符分类函数2. 字符转换函数3. strlen的使用和模拟实现4.strcpy的使用和模拟实现5.strcat的使用和模拟实现6.strcmp的使用和模拟实现7.strncpy的模拟和实现8.strncat的实现和模拟实现9.strncmp函数使用10.strstr的使用和模拟实现11.strtok函…