基于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实现的简单流量透传能力框架就开发完毕

posted on 2023-08-31 20:22  贝克田庄  阅读(238)  评论(0编辑  收藏  举报

导航