基于jvm-sandbox实现一个简单功能的全链路压测agent
目前我们已知chaosblade-exec-jvm是基于jvm-sandbox开发的混沌工程注入工具,我们可以基于jvm-sandbox创建一些其他的工具agent:流量回放agent、全链路压测agent等等
接下来我会用完全的代码实现一个可以流量透传、mock挡板、影子表数据落地等功能的压测agent:
MyAgentProject/
|-- agent/ (Agent 模块)
| |-- src/
| |-- pom.xml
|
|-- module/ (Module 模块)
| |-- src/
| |-- pom.xml
|
|-- instrument/ (Instrument 模块)
| |-- src/
| |-- pom.xml
|
|-- pom.xml (Parent POM)
Agent 模块
这个模块主要用于与控制平台交互,管理 Agent 的生命周期,包括安装、卸载和升级等。
- API Client: 与控制平台的 API 进行交互。
- Updater: 负责检查更新,并执行升级操作。
- Lifecycle Manager: 管理 Agent 的加载和卸载。
- Command Line Tools: 一些命令行工具用于手动管理 Agent。
Module 模块
这个模块主要包括一系列的子模块,每一个用于具体的任务,比如流量透传、方法 Mock 等。
- Common Interface: 定义所有模块都要实现的通用接口。
- Middleware Support: 包括一些预定义的中间件支持模块。
- User-Defined Modules: 用户可以按照规定的接口添加自己的模块。
Instrument 模块
这个模块是 Agent 的框架,负责程序的生命周期管理以及提供一些内置的命令工具。
- Instrumentation API: 提供用于字节码操纵的 API。
- Lifecycle API: 提供用于模块生命周期管理的 API。
- Built-in Tools: 一些内置的命令行或图形工具。
一:流量透传
在实现这个功能之前,我们需要明确目标服务之间的调用框架以及版本:
目标服务:采用spring boot web:2.1.2-release、feign-hystrix:10.1.0
通常公司在中间件层面流量透传能力基本已经实现。我们通过简单的例子来学习如何用jvm-sandbox来实现我们的功能
1.pom.xml添加相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>pressure-agent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<parent>
<groupId>com.alibaba.jvm.sandbox</groupId>
<artifactId>sandbox-module-starter</artifactId>
<version>1.2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.11</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
</project>
|
2. 通过spi实现module接口
package com.lchen;
import com.alibaba.jvm.sandbox.api.Information;
import com.alibaba.jvm.sandbox.api.Module;
import com.alibaba.jvm.sandbox.api.ProcessController;
import com.alibaba.jvm.sandbox.api.annotation.Command;
import com.alibaba.jvm.sandbox.api.event.BeforeEvent;
import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;
import com.alibaba.jvm.sandbox.api.listener.ext.EventWatcher;
import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;
import org.kohsuke.MetaInfServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import static com.alibaba.jvm.sandbox.api.event.Event.Type.BEFORE;
@MetaInfServices(Module.class)
@Information(id = "pressure-module")
public class PressurePassModule implements Module {
private static final Logger LOGGER = LoggerFactory.getLogger(PressurePassModule.class);
@Resource
private ModuleEventWatcher moduleEventWatcher;
@Command("enable-traffic-pass")
public void enableTrafficPass() {
final EventWatcher watcher = new EventWatchBuilder(moduleEventWatcher)
.onClass("javax.servlet.http.HttpServlet")
.includeSubClasses()
.includeBootstrap()
.onBehavior("service")
.onWatching()
.onWatch(event -> {
final BeforeEvent bEvent = (BeforeEvent) event;
Object[] params = bEvent.argumentArray;
Class<?> contentRequestWrapper = Class.forName("org.springframework.web.util.ContentCachingRequestWrapper", true, bEvent.javaClassLoader);
if (params.length >= 2 && contentRequestWrapper.isInstance(params[0])) {
Object contentRequestWrapperObj = params[0];
Method headerMethod = contentRequestWrapper.getMethod("getHeader", String.class);
Object result = headerMethod.invoke(contentRequestWrapperObj, "X-Pressure-Test");
LOGGER.info("testFlag:{}", result);
if (result != null) {
Cache.set(String.valueOf(result));
}
}
ProcessController.returnImmediately(null);
}, BEFORE);
}
@Command("enable-feign-pass")
public void enableFeignPass() {
LOGGER.info("enableFeignPass feign");
final EventWatcher watcher = new EventWatchBuilder(moduleEventWatcher)
.onClass("feign.SynchronousMethodHandler")
.includeSubClasses()
.includeBootstrap()
.onBehavior("executeAndDecode")
.onWatching()
.onWatch(event -> {
final BeforeEvent bEvent = (BeforeEvent) event;
Class<?> requestTemplateClass = Class.forName("feign.RequestTemplate", true, bEvent.javaClassLoader);
Object[] params = bEvent.argumentArray;
if (params.length >= 1 && requestTemplateClass.isInstance(params[0])) {
Object requestTemplateObj = params[0];
Method headerMethod = requestTemplateClass.getMethod("header", String.class, String[].class);
// 检查是否为压测流量(从 ThreadLocal 或其他地方)
String testFlag = Cache.get(); // 假设这是你的实现
LOGGER.info("testFlag feign:{}", testFlag);
if (testFlag != null) {
headerMethod.invoke(requestTemplateObj, "X-Pressure-Test", new String[]{testFlag});
}
}
}, BEFORE);
}
}
3. 通过threadlocal实现流量服务内部传递,因为父子线程以及线程池原因,普通的threadlocal不能满足,我们采用TransmittableThreadLocal实现
package com.lchen;
import com.alibaba.ttl.TransmittableThreadLocal;
public class Cache<T> {
private static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static void set(String request) {
threadLocal.set(request);
}
public static void remove() {
threadLocal.remove();
}
public static String get() {
return threadLocal.get();
}
}
这样我们基于sandbox实现的简单流量透传能力框架就开发完毕
浙公网安备 33010602011771号