测试域: 流量回放-工具篇jvm-sandbox,jvm-sandbox-repeater,gs-rest-service

 

JVM-Sandbox

 或Javassist,ASM  ,Byte Buddy

Jvm-Sandbox-Repeater架构_小小平不平凡的博客-CSDN博客

https://www.cnblogs.com/hong-fithing/p/16222644.html

流量回放框架jvm-sandbox-repeater的实践_做人,最重要的就是开心嘛的博客-CSDN博客

[jvm-sandbox-repeater 学习笔记][入门使用篇] 2 配置说明 · TesterHome

流量回放框架jvm-sandbox-repeater的实践【入门使用篇】1 repeater安装与启动(初尝repeater-console) - 知乎

[jvm-sandbox-repeater 学习笔记][入门使用篇] 1 安装与启动 · TesterHome
jvm-sandbox-repeater

JVM沙箱容器,一种JVM的非侵入式运行期AOP解决方案,如jstack,jmap等都是attach方式,也就是进程之间通信。

1.vivo流量录制回放平

月光宝盒(vivo流量录制回放平台)正式对外开源 - 简书

沙箱常见应用场景

  • 线上故障定位
  • 线上系统流控
  • 线上故障模拟
  • 方法请求录制和结果回放
  • 动态日志打印
  • 安全信息监测和脱敏

JVM-SANDBOX还能帮助你做很多很多,取决于你的脑洞有多大了。

目标人群 - 面向测试开发工程师

  • 线上有个用户请求一直不成功,我想在测试环境Debug一下,能帮我复现一下吗?
  • 压测流量不知道怎么构造,数据结构太复杂,压测模型也难以评估,有什么好的办法吗?
  • 不想写接口测试脚本了,我想做一个流量录制系统,把线上用户场景做业务回归,可能会接入很多服务系统,不想让每个系统都进行改造,有好的框架选择吗?
  • 我想做一个业务监控系统,对线上核心接口采样之后做一些业务校验,实时监控业务正确性。

如果你有以上的想法或需求, jvm-sandbox-repeater 都将是你的不二选择方案;框架基于JVM-Sandbox,拥有JVM-Sandbox的一切特性,同时封装了以下能力:

  • 录制/回放基础协议,可快速配置/编码实现一类中间件的录制/回放
  • 开放数据上报,对于录制结果可上报到自己的服务端,进行监控、回归、问题排查等上层平台搭建

Repeater核心原理:

 名词解释
  • 录制:把一次请求的入参、出参、下游RPC、DB、缓存等序列化并存储的过程
  • 回放:把录制数据还原,重新发起一次或N次请求,对特定的下游节点进行MOCK的过程
  • 入口调用:入口调用一般是应用的流量来源,比如http/dubbo,在调用过程中录制调用入参、返回值。回放时作为流量发起和执行结果对比依据
  • 子调用:子调用是调用执行过程中某次方法调用,区别于入口调用,该调用不作为回放发起录制时会记录该方法的入参、返回值,回放时用该返回值进行MOCK
  • MOCK:在回放时,被拦截的子调用不会发生真实调用,利用Sandbox的流程干预能力,将录制时的返回值直接返回
Repeater特性
  • 无侵入:无需修改代码,无需重启jvm
  • 通用性:支持所有JVM类型语言
  • 可插拔:随时启停、随时卸载
  • 扩展性:简单几行代码即可适配一个常用插件
Repeater应用场景
  • 业务回归
  • 架构感知
  • 问题排查
  • 压测流量
  • 线上监控

repeater-console体验

 

对性能的影响 

结论:线上采样率控制在一定范围内,性能影响可接受

推荐使用agent方式还是attach方式

针对录制回放场景,推荐使用attach方式启动,更灵活,更可控,需要注意attach瞬间的影响

 

attach 和 agent 启动模式对比

模式优点不足
attach1. 不需要启停应用,即插即用,随时停止
2. 更新配置不需要重启应用
1. 进行 java 回放的时候可能由于无法获取到对应实例而回放失败
2. 如果需要对 repeater 进行 debug,需要将 repeater 代码嵌入到被录制应用的代码中,不方便 debug3. 不可进行录制应用名和录制环境的配置,会被默认标记为 unknown
agent1. 进行 java 回放的时候能够获取到对应实例能够正常回放
2. 启动应用时开启调试,即可远程调试 repeater3. 支持配置录制应用名以及录制环境,方便在录制记录中进行区分
1. 启动/停止都需要重启应用
2. 更新配置也需要重启应用

如何动态推送配置

配置变更后,用户主动推送,配置实时生效,无需重启

如何快速搭建测试平台

直接部署 repeater-console 到生产环境,把 repeater-module 分发到目标机器,录制后回放到指定环境

repeater的核心能力是什么?

1. 通用录制/回放能力
  • 无侵入式录制HTTP/Java/Dubbo入参/返回值录制能力(业务系统无感知)
  • 基于 TTL提供多线程子调用追踪,完整追踪一次请求的调用路径
  • 入口请求(HTTP/Dubbo/Java)流量回放、子调用(Java/Dubbo)返回值Mock能力
2. 快速可扩展API实现
  • 录制/回放插件式架构
  • 提供标准接口,可通过配置/简单编码实现一类通用插件
3. standalone工作模式
  • 无需依赖任何服务端/存储,可以单机工作,提供录制/回放能力

repeater的可以应用到哪些场景?

1. 业务快速回归
  • 基于线上流量的录制/回放,无需人肉准备自动化测试脚本、准备测试数据
2. 线上问题排查
  • 录制回放提供"昨日重现"能力,还原线上真实场景到线下做问题排查和Debug
  • 动态方法入参/返回值录制,提供线上快速问题定位
3. 压测流量准备
  • 0成本录制HTTP/Dubbo等入口流量,作为压测流量模型进行压测
4. 实时业务监控
  • 动态业务监控,基于核心接口数据录制回流到平台,对接口返回数据正确性进行校验和监控

二. jvm-sandbox-repeater简介


jvm-sandbox-repeater是阿里在19年7月份的时候开源的流量录制回放工具,代码提供了录制回放的能力,以及一个简单的repeater-console的demo示例。github 地址:GitHub - alibaba/jvm-sandbox-repeater: A Java server-side recording and playback solution based on JVM-Sandbox。

jvm-sandbox-repeater框架基于JVM-Sandbox,具备了JVM-Sandbox的所有特点封装了以下能力:

1.录制/回放基础协议,可快速配置/编码实现一类中间件的录制/回放

2.开放数据上报,对于录制结果可上报到自己的服务端,进行监控、回归、问题排查等上层平台搭建

基于它,我们可以在业务系统无感知的情况下,快速扩展 api ,实现自己的插件,对流量进行录制,入口请求(HTTP/Dubbo/Java)流量回放、子调用(Java/Dubbo)返回值Mock能力。详细介绍可以看官方说明。

录制回放主要原理如下:

录制:如图,当repeater启动对service A的录制后,有请求到service A,sandbox感知到请求后通知repeater。repeater对事件进行给过滤和采样计算,对满足录制条件的请求会记录请求、响应、子调用和响应,序列化成后通知repeater-console进行处理和保存。

回放:回放时,用户请求repeater-console的回放接口,明确需要回放哪条录制数据。然后repeater-console通过调用repeater提供的回放任务接收接口下发回放任务。repeater在执行回放任务的过程中,会反序列化记录的wrapperRecord,根据信息构造相同的请求,对被挂载的任务进行请求,并跟踪回放请求的处理流程,以便记录回放结果以及执行mock动作。如图,当我们启用了redis插件,录制时,service A到reids等的子请求方法、参数、响应将被录制下来,回放时,当service A再对reids发起请求时,repeater会先判断是否需要mock,当需要mock时会根据回放上下文中的信息拼接出MockRequest,通过mock策略计算获取MockResponse。目前源码中是获取相似度100%的请求的响应来进行mock。回放结束,repeater会将回放信息和结果序列化后通知repeater-console进行处理和保存。

重点词语解释

一、jvm-sandbox与jvm-sandbox-repeater

JVM-SANDBOXjvm-sandbox-repeater
简介JVM沙箱容器,一种JVM的非侵入式运行期AOP解决方案基于JVM-Sandbox的录制/回放通用解决方案
jvm-sandbox-repeater是JVM-Sandbox生态体系下的重要模块,它具备了JVM-Sandbox的所有特点,插件式设计便于快速适配各种中间件,封装请求录制/回放基础协议,也提供了通用可扩展的各种丰富API。
目标群体1. BTRACE好强大,也曾技痒想做一个更便捷、更适合自己的问题定位工具,既可支持线上链路监控排查,也可支持单机版问题定位。
2. 有时候突然一个问题反馈上来,需要入参才能完成定位,但恰恰没有任何日志,甚至出现在别人的代码里,好想开发一个工具可以根据需要动态添加日志,最好还能按照业务ID进行过滤。3. 系统间的异常模拟可以使用的工具很多,可是系统内的异常模拟怎么办,加开关或是用AOP在开发系统中实现,好想开发一个更优雅的异常模拟工具,既能模拟系统间的异常,又能模拟系统内的异常。4. 好想获取行调用链路数据,可以用它识别场景、覆盖率统计等等,覆盖率统计工具不能原生支持,统计链路数据不准确。想自己开发一个工具获取行链路数据。5. 我想开发录制回放、故障模拟、动态日志、行链路获取等等工具,就算我开发完成了,这些工具底层实现原理相同,同时使用,要怎么消除这些工具之间的影响,怎么保证这些工具动态加载,怎么保证动态加载/卸载之后不会影响其他工具,怎么保证在工具有问题的时候,快速消除影响,代码还原如果你有以上研发诉求,那么你就是JVM-SANDBOX(以下简称沙箱容器)的潜在客户。沙箱容器提供:1. 动态增强类你所指定的类,获取你想要的参数和行信息甚至改变方法执行2. 动态可插拔容器框架
1. 线上有个用户请求一直不成功,我想在测试环境Debug一下,能帮我复现一下吗?
2. 压测流量不知道怎么构造,数据结构太复杂,压测模型也难以评估,有什么好的办法吗?3. 不想写接口测试脚本了,我想做一个流量录制系统,把线上用户场景做业务回归,可能会接入很多服务系统,不想让每个系统都进行改造,有好的框架选择吗?4. 我想做一个业务监控系统,对线上核心接口采样之后做一些业务校验,实时监控业务正确性。如果你有以上的想法或需求,jvm-sandbox-repeater 都将是你的不二选择方案;框架基于JVM-Sandbox,拥有JVM-Sandbox的一切特性,同时封装了以下能力:1. 录制/回放基础协议,可快速配置/编码实现一类中间件的录制/回放2. 开放数据上报,对于录制结果可上报到自己的服务端,进行监控、回归、问题排查等上层平台搭建
项目简介JVM-SANDBOX(沙箱)实现了一种在不重启、不侵入目标JVM应用的AOP解决方案。
沙箱的特性无侵入:目标应用无需重启也无需感知沙箱的存在类隔离:沙箱以及沙箱的模块不会和目标应用的类相互干扰可插拔:沙箱以及沙箱的模块可以随时加载和卸载,不会在目标应用留下痕迹多租户:目标应用可以同时挂载不同租户下的沙箱并独立控制高兼容:支持JDK[6,11]沙箱常见应用场景线上故障定位线上系统流控线上故障模拟方法请求录制和结果回放动态日志打印安全信息监测和脱敏JVM-SANDBOX还能帮助你做很多很多,取决于你的脑洞有多大了。实时无侵入AOP框架在常见的AOP框架实现方案中,有静态编织和动态编织两种。静态编织:静态编织发生在字节码生成时根据一定框架的规则提前将AOP字节码插入到目标类和方法中,实现AOP;动态编织:动态编织则允许在JVM运行过程中完成指定方法的AOP字节码增强.常见的动态编织方案大多采用重命名原有方法,再新建一个同签名的方法来做代理的工作模式来完成AOP的功能(常见的实现方案如CgLib),但这种方式存在一些应用边界:侵入性:对被代理的目标类需要进行侵入式改造。比如:在Spring中必须是托管于Spring容器中的Bean固化性:目标代理方法在启动之后即固化,无法重新对一个已有方法进行AOP增强要解决无侵入的特性需要AOP框架具备 在运行时完成目标方法的增强和替换。在JDK的规范中运行期重定义一个类必须准循以下原则1. 不允许新增、修改和删除成员变量2. 不允许新增和删除方法3. 不允许修改方法签名JVM-SANDBOX属于基于Instrumentation的动态编织类的AOP框架,通过精心构造了字节码增强逻辑,使得沙箱的模块能在不违反JDK约束情况下实现对目标应用方法的无侵入运行时AOP拦截。
repeater的核心能力是什么?
1. 通用录制/回放能力无侵入式录制HTTP/Java/Dubbo入参/返回值录制能力(业务系统无感知)基于TTL提供多线程子调用追踪,完整追踪一次请求的调用路径入口请求(HTTP/Dubbo/Java)流量回放、子调用(Java/Dubbo)返回值Mock能力2. 快速可扩展API实现录制/回放插件式架构提供标准接口,可通过配置/简单编码实现一类通用插件3. standalone工作模式无需依赖任何服务端/存储,可以单机工作,提供录制/回放能力repeater的可以应用到哪些场景?1. 业务快速回归基于线上流量的录制/回放,无需人肉准备自动化测试脚本、准备测试数据2. 线上问题排查录制回放提供"昨日重现"能力,还原线上真实场景到线下做问题排查和Debug动态方法入参/返回值录制,提供线上快速问题定位3. 压测流量准备0成本录制HTTP/Dubbo等入口流量,作为压测流量模型进行压测4. 实时业务监控动态业务监控,基于核心接口数据录制回流到平台,对接口返回数据正确性进行校验和监控
核心原理事件驱动、类隔离策略、类增强策略流量录制、流量回放

三 部署使用: 

1.1 环境准备

安装包括 repeater 安装、repeater-console 安装

目前安装和使用,需要 mac 或者 linux 系统下进行,如果在 windows 下进行可能会遇到安装路径出错导致安装失败或者运行失败的情况。

  • linux/Mac os
  • jdk 1.8+
  • maven 3.2+
  • 数据库 mysql 5.7+(repeater-console 可能用到)

PS:如果只是想简单运行,可以直接使用官方版本,参考 官方用户手册,以standalone模式把玩。

下载源码:

[root@k8s-worker27-65 jvm-sandbox-repeater]# git clone https://github.com/alibaba/jvm-sandbox-repeater.git

standalone 快速开始

bootstrap.sh:

[root@k8s-worker27-65 bin]# cat bootstrap.sh 
#!/usr/bin/env bash

# exit shell with err_code
# $1 : err_code
# $2 : err_msg
typeset HOME=/opt/data/fll

exit_on_err()
{
    [[ ! -z "${2}" ]] && echo "${2}" 1>&2
    exit ${1}
}

PID=$(ps -ef | grep "repeater-bootstrap.jar" | grep "java" | grep -v grep | awk '{print $2}')

expr ${PID} "+" 10 &> /dev/null

# if occurred error,exit
if [ ! $? -eq 0 ] || [ "" = "${PID}" ] ;then
    echo ""
else
    echo "found target pid exist, pid is ${PID}, kill it..."
    kill -9 ${PID}
fi

if [ ! -f "${HOME}/sandbox/sandbox-module/repeater-bootstrap.jar" ]; then
    echo "repeater-bootstrap.jar not found, try to install";
    sh ./install-local.sh || exit_on_err 1 "install repeater failed"
fi

${JAVA_HOME}/bin/java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 \
     -javaagent:${HOME}/sandbox/lib/sandbox-agent.jar=server.port=8820\;server.ip=0.0.0.0 \
     -Dapp.name=jettopro \
     -Dapp.env=sit \
     -jar ${HOME}/sandbox/sandbox-module/repeater-bootstrap.jar

install-local.sh: 

[root@k8s-worker27-65 bin]# cat install-local.sh 
#!/usr/bin/env bash

# repeater's target dir
REPEATER_TARGET_DIR=../target/repeater

typeset HOME=/opt/data/fll
typeset SANDBOX_HOME=/opt/data/fll/sandbox

# exit shell with err_code
# $1 : err_code
# $2 : err_msg
exit_on_err()
{
    [[ ! -z "${2}" ]] && echo "${2}" 1>&2
    exit ${1}
}

# package
sh ./package.sh || exit_on_err 1 "install failed cause package failed"

# extract sandbox to ${HOME}
#curl -s https://github.com/alibaba/jvm-sandbox-repeater/releases/download/v1.0.0/sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"
cat sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"
#cat sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"

# copy module to ~/.sandbox-module
#mkdir -p ${HOME}/.sandbox-module || exit_on_err 1 "permission denied, can not mkdir ~/.sandbox-module"
#cp -r ${REPEATER_TARGET_DIR}/* ${HOME}/.sandbox-module  || exit_on_err 1 "permission denied, can not copy module to ~/.sandbox-module"
cp -r ${REPEATER_TARGET_DIR}/* ${SANDBOX_HOME}/sandbox-module  || exit_on_err 1 "permission denied, can not copy module to ${SANDBOX_HOME}/sandbox-module"

package.sh :

[root@k8s-worker27-65 bin]# cat package.sh 
#!/usr/bin/env bash

# repeater's target dir
REPEATER_TARGET_DIR=../target/repeater

# exit shell with err_code
# $1 : err_code
# $2 : err_msg
exit_on_err()
{
    [[ ! -z "${2}" ]] && echo "${2}" 1>&2
    exit ${1}
}

# maven package the sandbox
mvn clean package -Dmaven.test.skip=true -f ../pom.xml || exit_on_err 1 "package repeater failed."

mkdir -p ${REPEATER_TARGET_DIR}/plugins
mkdir -p ${REPEATER_TARGET_DIR}/cfg

cp ./repeater-logback.xml ${REPEATER_TARGET_DIR}/cfg/repeater-logback.xml \
    && cp ./repeater.properties ${REPEATER_TARGET_DIR}/cfg/repeater.properties \
    && cp ./repeater-config.json ${REPEATER_TARGET_DIR}/cfg/repeater-config.json \
    && cp ../repeater-module/target/repeater-module-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/repeater-module.jar \
    && cp ../repeater-console/repeater-console-start/target/repeater-console.jar ${REPEATER_TARGET_DIR}/repeater-bootstrap.jar \
    && cp ../repeater-plugins/ibatis-plugin/target/ibatis-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/ibatis-plugin.jar \
    && cp ../repeater-plugins/java-plugin/target/java-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/java-plugin.jar \
    && cp ../repeater-plugins/mybatis-plugin/target/mybatis-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/mybatis-plugin.jar \
    && cp ../repeater-plugins/dubbo-plugin/target/dubbo-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/dubbo-plugin.jar \
    && cp ../repeater-plugins/redis-plugin/target/redis-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/redis-plugin.jar \
    && cp ../repeater-plugins/http-plugin/target/http-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/http-plugin.jar \
    && cp ../repeater-plugins/hibernate-plugin/target/hibernate-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/hibernate-plugin.jar \
    && cp ../repeater-plugins/spring-data-jpa-plugin/target/spring-data-jpa-plugin-*-jar-with-dependencies.jar ${REPEATER_TARGET_DIR}/plugins/spring-data-jpa-plugin.jar

# tar the repeater.tar
cd ../target/
tar -zcvf repeater-stable-bin.tar repeater/
cd -

echo "package repeater-stable-bin.tar finish."

repeater-logback.xml : 

[root@k8s-worker27-65 bin]# cat repeater-logback.xml 
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="10000">

    <appender name="REPEATER-FILE-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>/opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log.%d{yyyy-MM-dd}</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="REPEATER-FILE-APPENDER"/>
    </root>

</configuration>
[root@k8s-worker27-65 bin]# cat repeater-config.json 
{
  "useTtl" : true,
  "degrade" : false,
  "exceptionThreshold" : 1000,
  "sampleRate" : 10000,
  "pluginsPath" : null,
  "httpEntrancePatterns" : [ "^/greeting.*$" ],
  "javaEntranceBehaviors" : [ {
    "classPattern" : "hello.GreetingController",
    "methodPatterns" : [ "greeting" ],
    "includeSubClasses" : false
  } ],
  "javaSubInvokeBehaviors" : [],
  "pluginIdentities" : [ "http", "java-entrance", "java-subInvoke", "mybatis", "ibatis" ],
  "repeatIdentities" : [ "java", "http" ]
}

repeater.properties: 

[root@k8s-worker27-65 bin]# cat repeater.properties 
# 录制消息投递地址
broadcaster.record.url=http://192.168.1.65:8001/facade/api/record/save

# 回放结果投递地址
broadcaster.repeat.url=http://192.168.1.65:8001/facade/api/repeat/save

# 回放消息取数据地址
repeat.record.url=http://192.168.1.65:8001/facade/api/record/%s/%s

# 配置文件拉取地址
repeat.config.url=http://192.168.1.65:8001/facade/api/config/%s/%s

# 心跳上报配置
repeat.heartbeat.url=http://192.168.1.65:8001/module/report.json

# 是否开启脱机工作模式
repeat.standalone.mode=false

# 是否开启spring advice拦截
repeat.spring.advice.switch=false;

启动:

启动之前:

[root@k8s-worker27-65 jvm-sandbox-repeater]# cat  bin/repeater.properties 
# 录制消息投递地址
broadcaster.record.url=http://127.0.0.1:8001/facade/api/record/save

# 回放结果投递地址
broadcaster.repeat.url=http://127.0.0.1:8001/facade/api/repeat/save

# 回放消息取数据地址
repeat.record.url=http://127.0.0.1:8001/facade/api/record/%s/%s

# 配置文件拉取地址
repeat.config.url=http://127.0.0.1:8001/facade/api/config/%s/%s

# 心跳上报配置
repeat.heartbeat.url=http://127.0.0.1:8001/module/report.json

# 是否开启脱机工作模式
repeat.standalone.mode=true

# 是否开启spring advice拦截
repeat.spring.advice.switch=false;


# 是否开启脱机工作模式
repeat.standalone.mode=true 单击模式,且

[root@k8s-worker27-65 bin]# cat install-local.sh 
#!/usr/bin/env bash

# repeater's target dir
REPEATER_TARGET_DIR=../target/repeater

# exit shell with err_code
# $1 : err_code
# $2 : err_msg
exit_on_err()
{
    [[ ! -z "${2}" ]] && echo "${2}" 1>&2
    exit ${1}
}

# package
sh ./package.sh || exit_on_err 1 "install failed cause package failed"

# extract sandbox to ${HOME}
#curl -s https://github.com/alibaba/jvm-sandbox-repeater/releases/download/v1.0.0/sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"
cat sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"

# copy module to ~/.sandbox-module
mkdir -p ${HOME}/.sandbox-module || exit_on_err 1 "permission denied, can not mkdir ~/.sandbox-module"
cp -r ${REPEATER_TARGET_DIR}/* ${HOME}/.sandbox-module  || exit_on_err 1 "permission denied, can not copy module to ~/.sandbox-module"

 cat sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"  curl下载不下来,自己想办法然后用本地的

[root@k8s-worker27-65 bin]# ./bootstrap.sh

step1 开始录制 

[root@k8s-worker27-65 sandbox]# curl -s 'http://192.168.1.65:8001/regress/slogan?Repeat-TraceId=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>

执行结果如下:

访问链接时,repeater 插件通过 Repeat-TraceId=127000000001156034386424510000ed,唯一追踪到了这一次请求,后台服务返回了JAVA是世界上最好的语言!,repeater 把画面定格在了这一秒并将结果和 firstId 绑定

[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">Javascript是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">GO是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 

浏览器:

step2 开始回放
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">GO是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">GO是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">GO是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 

浏览器:

无论我们多少次访问这个地址,都将返回 Repeat-TraceId=127000000001156034386424510000ed 绑定的录制信息JAVA是世界上最好的语言!;如果重新访问 Slogan后又会将最新的返回结果绑定到 Repeat-TraceId=127000000001156034386424510000ed(为了快速演示,将链路追踪的标志提到参数中进行透传了)

光是执行官方用例,当然不能满足我们需要啦。我们来测试下,如果有多个 Repeat-TraceId ,是否可以分别录制?

# 录制一个 128 开头的 traceId ,返回结果是 java
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=128000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>您在 /var/spool/mail/root 中有新邮件

# 录制一个 129 开头的 traceId ,返回结果是 Python
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=129000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">Python是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 

# 回放前面 128 2次 的流量 结果相同
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=128000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=128000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 

# 回放前面 129 2次 的流量 结果相同
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=129000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">Python是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 
[root@k8s-worker27-65 sandbox]# curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=129000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">Python是世界上最好的语言!</h1>[root@k8s-worker27-65 sandbox]# 

看来确实是有效的。

录制目标服务

前面只是个简单的练手,实际用不用得了,当然的实际项目说话啦。

为了简单,此处使用了几个 spring boot 的示例项目当做实际项目使用。

  • restful-api

Getting Started | Building a RESTful Web Service

项目地址: https://github.com/chenhengjie123/gs-rest-service(官方文档: Getting Started | Building a RESTful Web Service ,在官方的基础上增加了请求日志打印的功能,便于查看回放效果)

clone 后,直接用 complete 里面的完整示例,当做被测程序。

[root@k8s-worker27-65 gs-rest-service]#  git clone https://github.com/spring-guides/gs-rest-service.git

切换到2.1.6.RELEASE tag
[root@k8s-worker27-65 gs-rest-service]# git branch  -a
* (分离自 2.1.6.RELEASE)
  completed
  hide-show
  main
  no_cat_no_toc
  refactor
  remotes/origin/HEAD -> origin/main
  remotes/origin/autowired-ctor
  remotes/origin/boot-2.7
  remotes/origin/categories
  remotes/origin/completed
  remotes/origin/gregturn-master
  remotes/origin/hide-show
  remotes/origin/main
  remotes/origin/no_cat_no_toc
  remotes/origin/refactor
[root@k8s-worker27-65 gs-rest-service]# git tag
0.1.0
1.4.1.RELEASE
1.4.2.RELEASE
1.4.3.RELEASE
1.5.1.RELEASE
1.5.10.RELEASE
1.5.2.RELEASE
1.5.5.RELEASE
1.5.9.RELEASE
2.0.0.RELEASE
2.0.1.RELEASE
2.0.2.RELEASE
2.0.3.RELEASE
2.0.5.RELEASE
2.0.8.RELEASE
2.1.3.RELEASE
2.1.4.RELEASE
2.1.6.RELEASE
edgware.release
edgware.sr2
finchley.sr2
maven

编译:
 

Build an executable JAR
You can run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources and run that. Building an executable jar makes it easy to ship, version, and deploy the service as an application throughout the development lifecycle, across different environments, and so forth.

If you use Gradle, you can run the application by using ./gradlew bootRun. Alternatively, you can build the JAR file by using ./gradlew build and then run the JAR file, as follows:

java -jar build/libs/gs-rest-service-0.1.0.jar

If you use Maven, you can run the application by using ./mvnw spring-boot:run. Alternatively, you can build the JAR file with ./mvnw clean package and then run the JAR file, as follows:

java -jar target/gs-rest-service-0.1.0.jar


访问:
http://localhost:8080/greeting

启动: 

[root@k8s-worker27-65 target]# ls
classes            generated-test-sources     gs-rest-service-0.1.0.jar.original  maven-status  surefire-reports
generated-sources  gs-rest-service-0.1.0.jar  maven-archiver                      nohup.out     test-classes
[root@k8s-worker27-65 target]# java -jar gs-rest-service-0.1.0.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2023-09-19 14:43:28.201  INFO 9844 --- [           main] hello.Application                        : Starting Application v0.1.0 on k8s-worker27-65 with PID 9844 (/root/work/traffic/app/gs-rest-service/complete/target/gs-rest-service-0.1.0.jar started by root in /root/work/traffic/app/gs-rest-service/complete/target)
2023-09-19 14:43:28.204  INFO 9844 --- [           main] hello.Application                        : The following profiles are active: dev
2023-09-19 14:43:29.328  INFO 9844 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-09-19 14:43:29.358  INFO 9844 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-09-19 14:43:29.358  INFO 9844 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.21]
2023-09-19 14:43:29.446  INFO 9844 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-09-19 14:43:29.446  INFO 9844 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1181 ms
2023-09-19 14:43:29.663  INFO 9844 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-09-19 14:43:29.856  INFO 9844 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-09-19 14:43:29.859  INFO 9844 --- [           main] hello.Application                        : Started Application in 2.142 

浏览器访问:

http://192.168.1.65:8080/greeting

http://192.168.1.65:8080/greeting?name=wubo

程序本身功能:当请求 http://localhost:8080/greeting?name=User 时,返回 {"id":2,"content":"Hello, User!"} 。其中 Hello 后面的名称根据请求参数的 name 自动替换,id 会自动递增。

接下来,按照官方的说明,进行操作:

step0 安装 sandbox 和插件到应用服务器

curl -s http://sandbox-ecological.oss-cn-hangzhou.aliyuncs.com/install-repeater.sh | sh

也可以在源码中:
[root@k8s-worker27-65 bin]# ./install-repeater.sh

可以省略,因为本地有源码直接编即可,无源码的时候需要从官网下载编译好的二进制jar 

修改repeater配置文件:

源码目录:

[root@k8s-worker27-65 bin]# pwd
/root/work/traffic/wubo/jvm-sandbox-repeater/bin
[root@k8s-worker27-65 bin]# ls
bootstrap.sh  install-local.sh     package.sh            repeater-logback.xml  sandbox-1.3.3-bin.tar
health.sh     install-repeater.sh  repeater-config.json  repeater.properties

注意:javaSubInvokeBehaviors,之前的配置遗漏了 javaSubInvokeBehaviors 的设定,会导致回放的时候返回没有被 mock 掉,看不出效果。下面为更正后的配置

[root@k8s-worker27-65 bin]# cat repeater-config.json 
{
  "useTtl" : true,
  "degrade" : false,
  "exceptionThreshold" : 1000,
  "sampleRate" : 10000,
  "pluginsPath" : null,
  "httpEntrancePatterns" : [ "^/greeting.*$" ],
  "javaEntranceBehaviors" : [],
  "javaSubInvokeBehaviors" : [ {
    "classPattern" : "hello.GreetingController",
    "methodPatterns" : [ "greeting" ],
    "includeSubClasses" : false
  } ],
  "pluginIdentities" : [ "http", "java-entrance", "java-subInvoke", "mybatis", "ibatis" ],
  "repeatIdentities" : [ "java", "http" ]
}

录制回放配置字段这个配置,主要依赖com.alibaba.jvm.sandbox.repeater.plugin.domain.RepeaterConfig类,参考之前的博文,引用下字段配置说明,如下所示:具体的配置含义,官方提供的链接相对路径有问题。可以直接看这个链接: RepeaterConfig.java

配置名配置含义参数说明备注
pluginIdedentities录制所使用的插件列表,配置了相应的插件名称,才能启用对应类别插件类别的录制插件名称有效值有:"http", "java-entrance", "java-subInvoke", "mybatis", "redis","ibatis","dubbo-consumer","dubbo-provider"1、插件配置生效还需要~/.sandbox-module/plugins/有对应的插件 jar 包。2、该参数有效值字段对应的取值是源码中实现了InvokePlugin的类的identity方法。
repeatIdentities回放所使用的插件列表,配置了对应的插件,才能进行对应类别的回放插件名称有效值有:"http", java", "dubbo"1、插件配置生效还需要~/.sandbox-module/plugins/有对应的插件 jar 包。2、该参数有效值字段对应的取值是源码中实现了Repeater的类的identity方法。
httpEntrancePatterns需要录制和回放的 http 接口,需要同时在 pluginIdedentities 和 repeatIdentities 中都配置了http这个配置才生效链接的路径参数支持正则表达式:"^/alertService/.*$"
javaSubInvokeBehaviors需要录制和 mock 的 java 方法的配置,需要 pluginIdedentities 配置了java-subInvoke这个配置才生效类名、方法名、以及是否包含子方法(若为 true,则匹配该类下的所有子类或者实现类,实际是否可用,有待验证),支持正则表达式如下配置的意思就是 com.test.server.utils 包下所有类和所有方法{"classPattern": "com.test.server.utils.","methodPatterns": [ "" ],"includeSubClasses": false}
javaEntranceBehaviors需要录制和回放的 java 方法的入口,需要同时在 pluginIdedentities 配置了java-entrance以及 repeatIdentities 配置了java这个配置才生效类名、方法名、以及是否包含子方法(若为 true,则匹配该类下的所有子类或者实现类,实际是否可用,有待验证),支持正则表达式如下配置的意思就是 com.test.utils 包下所有类和所有方法{"classPattern": "com.test.utils.","methodPatterns": [ "" ],"includeSubClasses": false}如果该入口方法在某个 http 入口的调用链路下,可能不会被录制到,如 com.test.controller.hello() 方法,本身对应着 “/hello 的访问路径,则录制时无法录制到以这个 hello 方法为入口的 java 录制记录”
pluginsPath插件路径String,默认填 null 即可默认填 null 即可
exceptionThreshold异常发生阈值;默认 1000 当ExceptionAware感知到异常次数超过阈值后,会降级模块Integer,默认填 1000 即可当前只使用过 1000,未出现过降级情况。当出现降级则不再进行任何录制。涉及的关键方法:com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener#access
degrade开启之后,不进行录制,只处理回放请求boolean,默认填 false 即可当前只使用过 false,按照字面理解就是当这个改为 true 之后,不再进行录制。涉及的关键方法:com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener#access
useTtl是否开启 ttl 线程上下文切换,开启之后,才能将并发线程中发生的子调用记录下来,否则无法录制到并发子线程的子调用信息,原理是将住线程的 threadLocal 拷贝到子线程,执行任务完成后恢复boolean,默认填 true 即可默认使用 true,开启线程跟踪
sampleRate采样率;最小粒度万分之一Integer 默认填 10000 即可当前只使用过 10000,可以结合这个方法理解com.alibaba.jvm.sandbox.repeater.plugin.core.trace.TraceContext#inTimeSample

 在哪里调整录制回放配置

1)在非 standalone 模式下,会从 repeater-console 的 /facade/api/config/${appName}/${env}接口中拉取配置。

2)在 standalone 模式下则读取~/.sandbox-module/cfg/repeater-config.json下的配置。

非 standalone 模式下

按照官方提供的例子,修改为com.alibaba.repeater.console.start.controller.ConfigFacadeApi#getConfig方法,重新组装RepeaterConfig对象。

修改之后,repeater-console 获取配置的接口需要重启后才能返回修改后的配置内容。

 package com.alibaba.repeater.console.start.controller;
/**
 * {@link ConfigFacadeApi} Demo工程;作为repeater录制回放的配置管理服务
 * <p>
 *
 * @author zhaoyb1990
 */
@RestController
@RequestMapping("/facade/api")
public class ConfigFacadeApi {

    @RequestMapping("/config/{appName}/{env}")
    public RepeaterResult<RepeaterConfig> getConfig(@PathVariable("appName") String appName,
                                                    @PathVariable("env") String env) {
        // 自己存配置;目前直接Mock了一份
        RepeaterConfig config = new RepeaterConfig();
        List<Behavior> behaviors = Lists.newArrayList();
        config.setPluginIdentities(Lists.newArrayList("http", "java-entrance", "java-subInvoke", "mybatis", "ibatis"));
        // 回放器
        config.setRepeatIdentities(Lists.newArrayList("java", "http"));
        // 白名单列表
        config.setHttpEntrancePatterns(Lists.newArrayList("^/regress/.*$"));
        // java入口方法
        behaviors.add(new Behavior("com.alibaba.repeater.console.service.impl.RegressServiceImpl", "getRegress"));
        config.setJavaEntranceBehaviors(behaviors);
        List<Behavior> subBehaviors = Lists.newArrayList();
        // java调用插件
        subBehaviors.add(new Behavior("com.alibaba.repeater.console.service.impl.RegressServiceImpl", "getRegressInner"));
        subBehaviors.add(new Behavior("com.alibaba.repeater.console.service.impl.RegressServiceImpl", "findPartner"));
        subBehaviors.add(new Behavior("com.alibaba.repeater.console.service.impl.RegressServiceImpl", "slogan"));
        config.setJavaSubInvokeBehaviors(subBehaviors);
        config.setUseTtl(true);
        return RepeaterResult.builder().success(true).message("operate success").data(config).build();
    }

}

可以自行调整这个接口,改为读取文件的模式,这样可以做到修改配置不需要重启 repeater-console。

standalone 模式下

直接修改~/.sandbox-module/cfg/repeater-config.json文件中的内容。

PS:修改后,如果本地重新安装了 repeater 则会恢复到没有修改的情况。如果想知道怎么样重新安装都不会被重置,那就看看 bin 目录下的install-local.shpackage.sh了解下安装过程都干了啥。

重启 repeater(可用)

repeater启动与关闭章节中的启动与关闭相关命令,进行 repeater 重启。启动过程将会重新从 repeater-console 拉取配置。standalone 模式下也会重新读取配置。

使用 repeaterModule 中的接口更新配置(不可用,repeater 有缺陷有待完善)

com.alibaba.jvm.sandbox.repeater.module.RepeaterModule类中,实现了推送配置更新的接口。

可通过访问http://${repeater.ip}:${repeater.port}/sandbox/default/module/http/repeater/pushConfig接口,将配置的内容序列化后传输过去。

但是由于 repeater 插件中只有 JavaSubInvokePlugin 插件实现了 onConfigChange 方法,所以这个接口功能并不完善。

repeater.properties

置文件主要是 repeater 是否以 standalone 模式运行,以及以非 standalone 模式运行时与 repeater-console 交互的 url 路径。

一般会在需要调整 repeater-console 地址的时候进行修改。repeat.standalone.mode一般用 false,使用非 standalone 模式。

实际生效的配置是位于~/.sandbox-module/cfg 中的 repeater.properties。

在项目的 bin 目录下也有一份 repeater.properties,这份是在执行安装脚本的时候会被复制到~/.sandbox-module/cfg 下的。

每次修改这份配置,都需要重启 repeater 才能生效。

如下的配置, http://127.0.0.1:8001 为 repeater-console 的地址。

 bootstrap.sh:需改目录结构

[root@k8s-worker27-65 bin]# mkdir -p /opt/data/fll/sandbox
[root@k8s-worker27-65 bin]# cat bootstrap.sh 
#!/usr/bin/env bash

# exit shell with err_code
# $1 : err_code
# $2 : err_msg
typeset HOME=/opt/data/fll

exit_on_err()
{
    [[ ! -z "${2}" ]] && echo "${2}" 1>&2
    exit ${1}
}

PID=$(ps -ef | grep "repeater-bootstrap.jar" | grep "java" | grep -v grep | awk '{print $2}')

expr ${PID} "+" 10 &> /dev/null

# if occurred error,exit
if [ ! $? -eq 0 ] || [ "" = "${PID}" ] ;then
    echo ""
else
    echo "found target pid exist, pid is ${PID}, kill it..."
    kill -9 ${PID}
fi

if [ ! -f "${HOME}/sandbox/sandbox-module/repeater-bootstrap.jar" ]; then
    echo "repeater-bootstrap.jar not found, try to install";
    sh ./install-local.sh || exit_on_err 1 "install repeater failed"
fi

${JAVA_HOME}/bin/java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 \
     -javaagent:${HOME}/sandbox/lib/sandbox-agent.jar=server.port=8820\;server.ip=0.0.0.0 \
     -Dapp.name=jettopro \
     -Dapp.env=sit \
     -jar ${HOME}/sandbox/sandbox-module/repeater-bootstrap.jar
[root@k8s-worker27-65 bin]# 

 install-local.sh:修改目录结构 

[root@k8s-worker27-65 bin]# cat install-local.sh 
#!/usr/bin/env bash

# repeater's target dir
REPEATER_TARGET_DIR=../target/repeater

typeset HOME=/opt/data/fll
typeset SANDBOX_HOME=/opt/data/fll/sandbox

# exit shell with err_code
# $1 : err_code
# $2 : err_msg
exit_on_err()
{
    [[ ! -z "${2}" ]] && echo "${2}" 1>&2
    exit ${1}
}

# package
sh ./package.sh || exit_on_err 1 "install failed cause package failed"

# extract sandbox to ${HOME}
#curl -s https://github.com/alibaba/jvm-sandbox-repeater/releases/download/v1.0.0/sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"
cat sandbox-1.3.3-bin.tar | tar xz -C ${HOME} || exit_on_err 1 "extract sandbox failed"
# copy module to ~/.sandbox-module
#mkdir -p ${HOME}/.sandbox-module || exit_on_err 1 "permission denied, can not mkdir ~/.sandbox-module"
#cp -r ${REPEATER_TARGET_DIR}/* ${HOME}/.sandbox-module  || exit_on_err 1 "permission denied, can not copy module to ~/.sandbox-module"
cp -r ${REPEATER_TARGET_DIR}/* ${SANDBOX_HOME}/sandbox-module  || exit_on_err 1 "permission denied, can not copy module to ${SANDBOX_HOME}/sandbox-module"

 repeater.properties:# 是否开启脱机工作模式
repeat.standalone.mode=true

[root@k8s-worker27-65 bin]# cat repeater.properties 
# 录制消息投递地址
broadcaster.record.url=http://127.0.0.1:8001/facade/api/record/save

# 回放结果投递地址
broadcaster.repeat.url=http://127.0.0.1:8001/facade/api/repeat/save

# 回放消息取数据地址
repeat.record.url=http://127.0.0.1:8001/facade/api/record/%s/%s

# 配置文件拉取地址
repeat.config.url=http://127.0.0.1:8001/facade/api/config/%s/%s

# 心跳上报配置
repeat.heartbeat.url=http://127.0.0.1:8001/module/report.json

# 是否开启脱机工作模式
repeat.standalone.mode=true

# 是否开启spring advice拦截
repeat.spring.advice.switch=false;

配置说明

repeater-logback.xml

该配置文件主要是控制 repeater 的日志打印路径地址以及打印等级。

一般会在需要调整日志等级的时候修改。

实际生效的配置是位于~/.sandbox-module/cfg 中的 repeater-logback.xml。

在项目的 bin 目录下也有一份 repeater-logback.xml,这份是在执行安装脚本的时候会被复制到~/.sandbox-module/cfg 下的。

每次修改这份配置,都需要重启 repeater 才能生效。

可以修改日志级别,默认是info

[root@k8s-worker27-65 bin]# cat repeater-logback.xml 
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="10000">

    <appender name="REPEATER-FILE-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>/opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log.%d{yyyy-MM-dd}</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="REPEATER-FILE-APPENDER"/>
    </root>

</configuration>

启动repeater-bootstrap.jar

[root@k8s-worker27-65 bin]# ./bootstrap.sh

检测日志: 如果没有日志,需要先启动一下step2 attach sandbox

[root@k8s-worker27-65 sandbox]# tailf /opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log

step2 attach sandbox 到目标进程

监测被测系统方式:

先到刚才 clone spring boot 示例项目的根目录,启动被测应用

[root@k8s-worker27-65 ~]# ps -ef |grep java
root     12449 30686  4 15:11 pts/1    00:00:27 java -jar gs-rest-service-0.1.0.jar
root     13573 30679 36 15:20 pts/0    00:00:30 java -jar repeater-bootstrap.jar
root     13684 12208  0 15:21 pts/4    00:00:00 grep --color=auto java

可以看到,进程 id 为 12449。然后开始 attach

cd ~/sandbox/bin

# 假设目标JVM进程号为'7306' 。-P 是设定 jvm-sandbox 的端口号,后面回放需要用到
[root@k8s-worker27-65 bin]# ./sandbox.sh -p 12449 -P 12580 
                    NAMESPACE : default
                      VERSION : 1.3.3
                         MODE : ATTACH
                  SERVER_ADDR : 0.0.0.0
                  SERVER_PORT : 12580
               UNSAFE_SUPPORT : ENABLE
                 SANDBOX_HOME : /root/sandbox/bin/..
            SYSTEM_MODULE_LIB : /root/sandbox/bin/../module
              USER_MODULE_LIB : /opt/data/fll/sandbox/sandbox-module;~/.sandbox-module;
          SYSTEM_PROVIDER_LIB : /root/sandbox/bin/../provider
           EVENT_POOL_SUPPORT : DISABLE
[root@k8s-worker27-65 bin]# 

小技巧:上述的找进程 id + attach 过程,可以用这个命令一键达成:

# -P 是设定 jvm-sandbox 的端口号,后面回放需要用到
sh ~/sandbox/bin/sandbox.sh -p `ps -ef | grep "target/gs-rest-service-0.1.0.jar" | grep -v grep | awk '{print $2}'` -P 12580

查看 repeater 日志看模块和插件加载情况

[root@k8s-worker27-65 bin]# tailf -200 ~/logs/sandbox/repeater/repeater.log

或者agent方式:

 配置文件:

{
  "useTtl" : true,
  "degrade" : false,
  "exceptionThreshold" : 1000,
  "sampleRate" : 10000,
  "pluginsPath" : null,
  "httpEntrancePatterns" : [ "^/jettopro-basic/.*$" ],
  "javaEntranceBehaviors" : [],
  "javaSubInvokeBehaviors" : [ {
    "classPattern" : "com.cn.jettech.jettoprobasic.controller.basiccontroller02.BasicController201",
    "methodPatterns" : [ "basichello20101" ],
    "includeSubClasses" : false
  } ],
  "pluginIdentities" : [ "http", "java-entrance", "java-subInvoke", "mybatis", "ibatis" ],
  "repeatIdentities" : [ "java", "http" ]
}

 

{
  "useTtl" : true,
  "degrade" : false,
  "exceptionThreshold" : 1000,
  "sampleRate" : 10000,
  "pluginsPath" : null,
  "httpEntrancePatterns" : [ "^/greeting.*$" ],
  "javaEntranceBehaviors" : [],
  "javaSubInvokeBehaviors" : [ {
    "classPattern" : "hello.GreetingController",
    "methodPatterns" : [ "greeting" ],
    "includeSubClasses" : false
  } ],
  "pluginIdentities" : [ "http", "java-entrance", "java-subInvoke", "mybatis", "ibatis" ],
  "repeatIdentities" : [ "java", "http" ]
}

注意事项:
1) sandbox-agent.jar :自己安装的位置进行配置
2)录制应用名、录制环境 :与console配置管理中的一致
3)repeater启动端口 :未在进程中使用过的
4)application.jar:被测应用
java
-javaagent:${HOME}/sandbox/lib/sandbox-agent.jar=server.port=${repeater启动端口}\;server.ip=0.0.0.0 \
-Dapp.name=${录制应用名} \
-Dapp.env=${录制环境} \
-jar application.jar
[root@k8s-worker27-65 target]# ls /opt/data/fll/sandbox/lib/
sandbox-agent.jar  sandbox-core.jar  sandbox-spy.jar

注册两个

[root@k8s-worker27-65 target]# nohup  java -javaagent:/opt/data/fll/sandbox/lib/sandbox-agent.jar=server.port=12580\;server.ip=192.168.1.65 -Dapp.name=gs -Dapp.env=sit -jar /root/work/traffic/app/gs-rest-service/complete/target/gs-rest-service-0.1.0.jar &


[root@k8s-worker27-65 app]# nohup java -javaagent:/opt/data/fll/sandbox/lib/sandbox-agent.jar=server.port=12581\;server.ip=192.168.1.65 -Dapp.name=jettopro -Dapp.env=dev -jar /root/work/traffic/app/jettopro-basic-0.0.1-SNAPSHOT.jar &

 此时已经注册上来了有心跳了

step3 开始录制和回放

录制几个请求:

[root@k8s-worker27-65 sandbox]# curl -s 'http://localhost:8080/greeting?name=jettech01'
{"id":57,"content":"Hello, jettech01!"}
[root@k8s-worker27-65 sandbox]# curl -s 'http://localhost:8080/greeting?name=jettech02'
{"id":58,"content":"Hello, jettech02!"}
[root@k8s-worker27-65 sandbox]# curl -s 'http://localhost:8080/greeting?name=jettech03'
{"id":59,"content":"Hello, jettech03!"}

对应看到 repeater 的日志增加了几个输出:

root@k8s-worker27-65 sandbox]# tailf /opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log 
	at com.alibaba.jvm.sandbox.repeater.module.RepeaterModule.access$500(RepeaterModule.java:64)
	at com.alibaba.jvm.sandbox.repeater.module.RepeaterModule$1.run(RepeaterModule.java:142)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
2023-09-19 16:58:13 INFO  enable plugin mybatis success
2023-09-19 16:58:13 INFO  add watcher success,type=mybatis,watcherId=1004
2023-09-19 16:58:13 INFO  enable plugin http success
2023-09-19 16:58:13 INFO  add watcher success,type=http,watcherId=1006
2023-09-19 16:58:13 INFO  register event bus success in repeat-register

2023-09-19 17:19:09 INFO  broadcast success,traceId=192168001065169511514921110001ed,resp=success
2023-09-19 17:19:12 INFO  broadcast success,traceId=192168001065169511515248010002ed,resp=success
2023-09-19 17:19:18 INFO  broadcast success,traceId=192168001065169511515821410003ed,resp=success

好了,试试回放。

录播的数据在本地,当然也可以纯在数据库里面,后面会存

[root@k8s-worker27-65 sandbox]# ls /opt/data/fll/sandbox/sandbox-module/repeater-data/record/
192168001065169511514921110001ed  192168001065169511515248010002ed  192168001065169511515821410003ed

如:其实是序列化之后存起来 

[root@k8s-worker27-65 sandbox]# cat  /opt/data/fll/sandbox/sandbox-module/repeater-data/record/192168001065169511514921110001ed
QzA6Y29tLmFsaWJhYmEuanZtLnNhbmRib3gucmVwZWF0ZXIucGx1Z2luLmRvbWFpbi5SZWNvcmRNb2RlbJcOc3ViSW52b2NhdGlvbnMSZW50cmFuY2VJbnZvY2F0aW9uB3RyYWNlSWQEaG9zdAtlbnZpcm9ubWVudAdhcHBOYW1lCXRpbWVzdGFtcGB5QzA5Y29tLmFsaWJhYmEuanZtLnNhbmRib3gucmVwZWF0ZXIucGx1Z2luLmRvbWFpbi5JbnZvY2F0aW9unQhpZGVudGl0eQR0eXBlDnNlcmlhbGl6ZVRva2VuA2VuZAVzdGFydBN0aHJvd2FibGVTZXJpYWxpemVkEnJlc3BvbnNlU2VyaWFsaXplZBFyZXF1ZXN0U2VyaWFsaXplZAhlbnRyYW5jZQVpbmRleAd0cmFjZUlkCXByb2Nlc3NJZAhpbnZva2VJZGFDMDdjb20uYWxpYmFiYS5qdm0uc2FuZGJveC5yZXBlYXRlci5wbHVnaW4uZG9tYWluLklkZW50aXR5kQN1cmliME1qYXZhOi8vaGVsbG8uR3JlZXRpbmdDb250cm9sbGVyL2dyZWV0aW5nfihMamF2YS9sYW5nL1N0cmluZzspTGhlbGxvL0dyZWV0aW5nO0MwOWNvbS5hbGliYWJhLmp2bS5zYW5kYm94LnJlcGVhdGVyLnBsdWdpbi5kb21haW4uSW52b2tlVHlwZZEEbmFtZWMEamF2YTA2b3JnLnNwcmluZ2ZyYW1ld29yay5ib290LmxvYWRlci5MYXVuY2hlZFVSTENsYXNzTG9hZGVyTAAAAYqsvH/VTAAAAYqsvH+nTjBEUXc1b1pXeHNieTVIY21WbGRHbHVaNUlIWTI5dWRHVnVkQUpwWkdBUlNHVnNiRzhzSUdwbGRIUmxZMmd3TVNINE9RPT0cY1FkYmIySnFaV04wQ1dwbGRIUmxZMmd3TVE9PUaRMCAxOTIxNjgwMDEwNjUxNjk1MTE1MTQ5MjExMTAwMDFlZMvqy+pDMD1jb20uYWxpYmFiYS5qdm0uc2FuZGJveC5yZXBlYXRlci5wbHVnaW4uZG9tYWluLkh0dHBJbnZvY2F0aW9upghpZGVudGl0eQR0eXBlCXBhcmFtc01hcAdoZWFkZXJzDnNlcmlhbGl6ZVRva2VuA2VuZAVzdGFydBN0aHJvd2FibGVTZXJpYWxpemVkEnJlc3BvbnNlU2VyaWFsaXplZBFyZXF1ZXN0U2VyaWFsaXplZAhlbnRyYW5jZQVpbmRleAd0cmFjZUlkCXByb2Nlc3NJZAhpbnZva2VJZAVhc3luYwRib2R5C2NvbnRlbnRUeXBlBm1ldGhvZARwb3J0CnJlcXVlc3RVUkkKcmVxdWVzdFVSTGRiEWh0dHA6Ly8vZ3JlZXRpbmcvYwRodHRwSARuYW1lcQdbc3RyaW5nCWpldHRlY2gwMVpIBGhvc3QObG9jYWxob3N0OjgwODAKdXNlci1hZ2VudAtjdXJsLzcuMjkuMAZhY2NlcHQDKi8qWjA2b3JnLnNwcmluZ2ZyYW1ld29yay5ib290LmxvYWRlci5MYXVuY2hlZFVSTENsYXNzTG9hZGVyTAAAAYqsvH/ZTAAAAYqsvH+kTjA4TUNkN0ltbGtJam8xTnl3aVkyOXVkR1Z1ZENJNklraGxiR3h2TENCcVpYUjBaV05vTURFaEluMD0xHGNRZGJiMkpxWldOMFNBZG9aV0ZrWlhKelNBUm9iM04wRG14dlkyRnNhRzl6ZERvNE1EZ3dDblZ6WlhJdFlXZGxiblFMWTNWeWJDODNMakk1TGpBR1lXTmpaWEIwQXlvdktsb0pjR0Z5WVcxelRXRndTQVJ1WVcxbGNRZGJjM1J5YVc1bkNXcGxkSFJsWTJnd01Wb0diV1YwYUc5a0EwZEZWQVJ3YjNKMDFCK1FDbkpsY1hWbGMzUlZVa3dlYUhSMGNEb3ZMMnh2WTJGc2FHOXpkRG80TURnd0wyZHlaV1YwYVc1bkNuSmxjWFZsYzNSVlVra0pMMmR5WldWMGFXNW5CR0p2WkhrQUMyTnZiblJsYm5SVWVYQmxUbG89VJEwIDE5MjE2ODAwMTA2NTE2OTUxMTUxNDkyMTExMDAwMWVky+nL6UYATgNHRVTUH5AJL2dyZWV0aW5nHmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVldGluZzAgMTkyMTY4MDAxMDY1MTY5NTExNTE0OTIxMTEwMDAxZWQMMTkyLjE2OC4xLjY1B3Vua25vd24HdW5rbm93bkwAAAGKrLx/pA==[root@k8s-worker27-65 sandbox]#

方式一:利用模块暴露的 http 接口发起回放

官方的说明:

模块暴露了回放接口,用于服务端发起远程回放,具体如下:

 

url :  http://ip:port/sandbox/default/module/http/repeater/repeat


params : _data其中 port 是 jvm-sandbox 启动时候绑定的 port,可以在 attach sandbox 时增加-P 12580 指定,或者执行~/sandbox/bin/sandbox.sh -p {pid} -v 查看 SERVER_PORT _data 是由 RepeatMeta 经过 hessian 序列化之后的值,具体调用方式参见 AbstractRecordService
和 RecordFacadeApi

没说明是用什么 http 方法(后面通过看  AbstractRecordService.java 看出是 post ),而且 _data 需要用程序做 RepeatMeta 的 hessian 序列化。。。看起来就不是给我们这种命令行触发用的。先跳过。

方式二:针对 HTTP 接口,可以像 Slogan Demo 一样进行参数或者 Header 透传方式进行 MOCK 回放

从前面的 repeater 日志,找到了几个 traceId 。对应把它填到 Repeat-TraceId-X 参数中。(特别留意:回放会根据录制时的 url 进行匹配。如果有参数是通过 url 传递的,必须录制和回放都用一样的参数

[root@k8s-worker27-65 sandbox]# curl -s 'http://localhost:8080/greeting?name=jettech01' -H "Repeat-TraceId-X:192168001065169511514921110001ed"
{"id":57,"content":"Hello, jettech01!"}
[root@k8s-worker27-65 sandbox]# curl -s 'http://localhost:8080/greeting?name=jettech02' -H "Repeat-TraceId-X:192168001065169511515248010002ed"
{"id":58,"content":"Hello, jettech02!"}
[root@k8s-worker27-65 sandbox]# curl -s 'http://localhost:8080/greeting?name=jettech03' -H "Repeat-TraceId-X:192168001065169511515821410003ed"
{"id":59,"content":"Hello, jettech03!"}

id 还在递增,回放没生效。但看了下  plugin 的源码 ,确实是有这样的逻辑。而且上面两个请求发出的时候, repeater.log 并没有输出录制到请求的日志。

20190710 更新:问题已解决,原因是前面的 repeater.json 配置不正确,遗漏了 javaSubInvokeBehaviors 相关配置,导致返回值没有被录制到。

修正后,已经可以输出正确的返回了。此时 repeater.log 也会对应输出日志:

tailf /opt/data/fll/sandbox/logs/sandbox/repeater/repeater.log 


2023-09-19 17:19:09 INFO  broadcast success,traceId=192168001065169511514921110001ed,resp=success
2023-09-19 17:19:12 INFO  broadcast success,traceId=192168001065169511515248010002ed,resp=success
2023-09-19 17:19:18 INFO  broadcast success,traceId=192168001065169511515821410003ed,resp=success
2023-09-19 17:22:18 INFO  find target invocation by PARAMETER_MATCH,identity=java://hello.GreetingController/greeting~(Ljava/lang/String;)Lhello/Greeting;,invocation=com.alibaba.jvm.sandbox.repeater.plugin.domain.Invocation@7fe7d33e
2023-09-19 17:22:34 INFO  find target invocation by PARAMETER_MATCH,identity=java://hello.GreetingController/greeting~(Ljava/lang/String;)Lhello/Greeting;,invocation=com.alibaba.jvm.sandbox.repeater.plugin.domain.Invocation@f5ea90e
2023-09-19 17:22:50 INFO  find target invocation by PARAMETER_MATCH,identity=java://hello.GreetingController/greeting~(Ljava/lang/String;)Lhello/Greeting;,invocation=com.alibaba.jvm.sandbox.repeater.plugin.domain.Invocation@1ae27c65

最后三行就是对应返回录制的 response 了

方式三:使用 repeater-console 做回放

官方文档没有明确给出这个方式,但通过查看 repeater-console 里面的 readme ,可以看到它也是有暴露接口供调用的。因此也试试。

结果看了下,里面提供的 standalone 和 mysql 两种数据存储方式,都不支持前面回放的存储方法(存在 ~/.sandbox-module/repeater-data/record 中)。还得调整录制方式才能进行回放。

整体结构还是比较清晰的,有 plugin 目录,便于扩展。也有 console 提供最简要的流量管理。更详细的,后续再慢慢研究。 

repeater-console 简介

官方的说明:

jvm-sandbox-repeater 仅仅提供了录制回放的能力,如果需要完成业务回归实时监控压测等平台,后面须要有一个数据中心负责采集数据的加工、存储、搜索,repeater-console 提供了简单的 demo 示例;一个模块管理平台负责管理 JVM-Sandbox 各模块生命周期;一个配置管理平台负责维护和推送 jvm-sandbox-repeater 采集所须要的各种配置变更

 

 

注意:目前项目代码默认启动 standalone 模式,不需要依赖任何服务端和存储,能够简单快速的实现单机的录制回放,控制单机模式的开关在~/.sandbox-module/cfg/repeater.properties 文件中的 repeat.standalone.mode=true //开启或关闭单机工作模式,关闭单机模式后,配置拉取/消息投递等都依赖 repeater.properties 中配置的具体 url;如不想通过 http 拉取和消息投递的也可以自己实现BroadcasterConfigManager。稍后我们会公布一份录制回放所需的完整架构图以及 jvm-sandbox-repeater 在整个体系中的位置供大家工程使用做参考。

个人理解,要想在业务中使用,我们还得搞下 数据中心模块管理 和 配置管理 。

【数据中心】:你存了那么多流量,总得有个存储和管理的地方吧,数据中心就是干这个活。要不光靠官方提供的那个透传 repeatId 的回放方法,只能回放单个流量,实际项目不够用。
【模块管理】:个人理解是各个 plugin 的管理
【配置管理】:就是之前试用时说过的只有一个 ~/.sandbox-module/cfg/repeater-config.json 配置文件,是不可能满足多个项目同时使用的需要的。所以需要有个配置管理,提供这方面配置的存储和修改能力。

源码熟悉

由于目前官方对于这个 console 只有一份非常简单的文档:

repeater-console 工程集成录制/回放的配置管理;数据存储/数据对比等具备多种能力,因各系统架构差异较大,目前仅开源简单的 demo 工程,后续会提供统一的工程,也希望有能力和时间的同学来提 PR

curl -s http://127.0.0.1:8001/regress/getAsync/repeater -H
'Repeat-TraceId:030010083212156034386424510101ed'
curl -s http://127.0.0.1:8001/facade/api/repeat/repeater/xxxxxxxxxxxxxxxxx-H "RepeatId:xxxxxxxxxxxxxxxxx" 
curl -s http://127.0.0.1:8001/facade/api/repeat/callback/xxxxxxxxxxxxxxxxx

所以只能通过解读源码来反推用法咯。

个人的源码阅读三步骤:明确阅读目的、了解整体架构、细读目标功能

step 0 明确阅读目的

目的很简单,使用 repeater-console ,在目前的 demo 项目上完成批量流量录制回放的功能

step 1 了解整体架构

为了便于描述,还是用 tree 吧。

特别说明:以下均为个人分析,并不保证正确哈。

tree -L 10 | grep -v iml | grep -v target
.
├── Readme.md
├── pom.xml
├── repeater-console-common      // 存放公共方法的模块
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── alibaba
│                       └── repeater
│                           └── console
│                               └── common
│                                   ├── PackageInfo.java // 一个空的类,应该是预留用的
│                                   └── domain           // 目前只有一个名为 Regress 的 java bean ,代表单条回放记录
├── repeater-console-dal         // 和数据库打交道的存储模块,model 层
│   ├── pom.xml
│   └── src
│       └── main
│           ├── java
│           │   └── com
│           │       └── alibaba
│           │           └── repeater
│           │               └── console
│           │                   └── dal
│           │                       ├── mapper  // mybatis 的 mapper 映射类,存放数据库操作犯法
│           │                       └── model   // mybatis 的 model 类,和数据库表结构对应
│           └── resources
│               └── database.sql                // 数据库初始化语句
├── repeater-console-service   // 主要逻辑实现的模块,service 层
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── alibaba
│                       └── repeater
│                           └── console
│                               └── service
│                                   ├── RecordService.java  // 存储服务,提供存储录制、存储回放、获取记录、执行回放、查看回放结果接口的定义
│                                   ├── RegressService.java // 回归服务,提供获取单个回放、多个回放、找到你的小伙伴、slogan喊口号4个接口的定义(最后两个接口不知道是什么鬼。。。)
│                                   ├── impl
│                                   │   ├── AbstractRecordService.java // 存储服务一个抽象实现,提供了 repeat 方法和 jvm-sandbox-repeater 进行交互,触发回放
│                                   │   ├── RecordServiceLocalImpl.java // 存储服务的本地存储实现。使用一个 ConcurrentHashMap 把所有数据存到内存中。
│                                   │   ├── RecordServiceMysqlImpl.java // 存储服务 mysql 存储的实现。使用前面存储模块和 mysql 数据库交互,进行存储。
│                                   │   ├── RecordServiceProxyImpl.java // 存储服务的代理类,根据配置文件值来决定用哪个实现类进行存储服务的实现
│                                   │   └── RegressServiceImpl.java // 回归服务的实现类。包含了官方提供的 slogan 服务的实现。
│                                   └── util
│                                       └── ConvertUtil.java // 给原始录制记录加上一些元数据(如 appName,environment 等),并转换成一个完整的录制记录的工具类。转换方法目前各个存储服务用的都是 hessian 序列化。
├── repeater-console-start   // 最外部的层,controller 层。直接暴露接口和提供 main 入口。我们最前面 slogan 示例看到的 repeater-bootstrap.jar 包,实际就是用这里源码打出来的包。
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── alibaba
│       │   │           └── repeater
│       │   │               └── console
│       │   │                   └── start
│       │   │                       ├── Application.java   // 标准的 spring boot 启动类
│       │   │                       ├── ConfigurationBean.java // java 回放用的感知 spring context 的 hook 
│       │   │                       └── controller
│       │   │                           ├── ConfigFacadeApi.java  // 配置管理服务 api 设计的示例。仅提供了获取配置的方法,而且直接 hard code 了一份配置。
│       │   │                           ├── RecordFacadeApi.java  // 存储服务 api 设计的示例,提供了存储录制、存储回放、获取记录、执行回放、查看回放结果五个 api 接口
│       │   │                           └── RegressController.java // 回归服务,相当于一个示例的被测服务。官方的 slogan 例子用的就是这里的接口。
│       │   └── resources
│       │       └── application.properties // 配置文件。需要留意的是,里面有个 `repeat.repeat.url` 配置项,需要和 sandbox 的监听 port 保持一致。
│       └── test
│           └── java
│               └── com
│                   └── alibaba
│                       └── repeater
│                           └── console
│                               └── start
│                                   └── RegressTest.java  // 一个自动化集成测试用例,如果在 idea 里面跑的话,需要先手动启动 console 服务才能运行,且测试了下,全部用例都是 fail 的。先忽略。

简单小结:

1、console 划分为了 4 个子模块,除了一个是公共模块外,剩余三个分别是数据层、service 逻辑层和最外部的 controller 层,基本是一个标准 spring boot 程序。
2、里面主要提供了 3 个服务:存储服务,配置管理服务,回归服务(本质上就是个示例,估计是给自动化测试用的)
3、需要重点关注的是存储服务,里面包含了存储录制、存储回放、获取记录、执行回放、查看回放结果五个 api 接口。

step 3 细读目标功能

从上一步已经明确了,目标功能是存储服务。因此进一步细看对应的代码。主要关注存储服务的实现。为了简便理解,主要针对 local 这个本地存储的实现进行解读。

里面涉及几个 model 定义,为了方便理解,先说明下:

  • RecordWrapper: repeater 提供的一个完整的录制记录。包括 appName、环境名、主机名、traceId、入口描述、入口调用记录、子调用记录。
  • RepeatModel: repeater 提供的一个回放结果记录。包括 repeatId、是否完成、实际返回值、原始返回值、diff 记录、耗时、traceId。
  • Record: console 提供的单个录制记录的描述,包括创建时间、录制时间、appName、主机名、traceId、原始录制记录。用途估计是后续用来过滤筛选记录。

下面的解读主要涉及上述 3 个类,更详细的领域模型划分,建议参考  domain

  • 添加录制的记录
    @Override
    public RepeaterResult<String> saveRecord(String body) {
        try {
            // 把输入值反序列化成 RecordWrapper 对象
            RecordWrapper wrapper = SerializerWrapper.hessianDeserialize(body, RecordWrapper.class);
            // 如果反序列化失败,直接返回错误
            if (wrapper == null || StringUtils.isEmpty(wrapper.getAppName())) {
                return RepeaterResult.builder().success(false).message("invalid request").build();
            }
            // 把 wrapper + 原始传入的 body ,组合成 record 。主要是添加了一个创建日期、大部分 wrapper 和 record 一一对应地存储,以及把整个 body 放到 wrapperRecord 对象中作为存档
            Record record = ConvertUtil.convertWrapper(wrapper, body);
            // 存到record的缓存里,key 是 appName + traceId 组合而成,value 就是 record 对象
            recordCache.put(buildUniqueKey(wrapper.getAppName(), wrapper.getTraceId()), record);
            // 保存成功,就可以返回了
            return RepeaterResult.builder().success(true).message("operate success").data("-/-").build();
        } catch (Throwable throwable) {
            return RepeaterResult.builder().success(false).message(throwable.getMessage()).build();
        }
    }

  • 添加回放的结果
    @Override
    public RepeaterResult<String> saveRepeat(String body) {
        try {
            // 相同的套路,先反序列化出 RepeatModel 对象
            RepeatModel rm = SerializerWrapper.hessianDeserialize(body, RepeatModel.class);
    
            // 从缓存中根据 repeatId 获取到录制的记录。特别留意,虽然 value 类型一样,但 record 和 repeat 是两个分别独立的缓存,所以这里的调整是不会影响上面 record 的调整的。
            Record record = repeatCache.remove(rm.getRepeatId());
            // 如果找不到记录,那就认为无效(repeatCached的记录添加,在执行回放的接口里会进行。所以如果找不到记录,说明这次回放的执行不是通过这个服务进行的,所以也没必要记录它的回放结果)
            if (record == null) {
                return RepeaterResult.builder().success(false).message("invalid repeatId:" + rm.getRepeatId()).build();
            }
    
            // 校验确认这个回放是通过这个服务执行后,取出原始的回放记录,并转成 RecordWrapper 对象,便于获取更多信息
            RecordWrapper wrapper = SerializerWrapper.hessianDeserialize(record.getWrapperRecord(), RecordWrapper.class);
            // 添加原始 response 信息
            rm.setOriginResponse(SerializerWrapper.hessianDeserialize(wrapper.getEntranceInvocation().getResponseSerialized()));
            // 把 repeatModel 记录到缓存
            repeatModelCache.put(rm.getRepeatId(), rm);
        } catch (Throwable throwable) {
            return RepeaterResult.builder().success(false).message(throwable.getMessage()).build();
        }
        return RepeaterResult.builder().success(true).message("operate success").data("-/-").build();
    }

  • 根据应用名和 traceId ,获取序列化后的录制数据
    @Override
    public RepeaterResult<String> get(String appName, String traceId) {
        // 从缓存中找数据,找不到就返回失败
        Record record = recordCache.get(buildUniqueKey(appName, traceId));
        if (record == null) {
            return RepeaterResult.builder().success(false).message("data not exits").build();
        }
    
        // 返回成功,数据为 wrapperRecord ,即序列化后的数据
        return RepeaterResult.builder().success(true).message("operate success").data(record.getWrapperRecord()).build();
    }

  • 根据 appName、traceId、repeatId 执行回放记录
    @Override
    public RepeaterResult<String> repeat(String appName, String traceId, String repeatId) {
        // 从录制记录里获取录制信息,如果找不到,返回失败
        final Record record = recordCache.get(buildUniqueKey(appName, traceId));
        if (record == null) {
            return RepeaterResult.builder().success(false).message("data does not exist").build();
        }
    
        // 执行回放
        RepeaterResult<String> pr = repeat(record, repeatId);
    
        // 如果成功,以执行结果的 data 字段(成功时是 repeatId)为 key ,录制记录为 value ,记录到 repeatCache 中
        if (pr.isSuccess()) {
            repeatCache.put(pr.getData(), record);
        }
        return pr;
    }

  • 根据 repeatId 获取回放执行结果
    @Override
    public RepeaterResult<RepeatModel> callback(String repeatId) {
        // 因为保存回放记录时会移除 repeatCache 里的记录。如果发现里面没被移除,说明回放未结束,返回还在进行中
        if (repeatCache.containsKey(repeatId)) {
            return RepeaterResult.builder().success(true).message("operate is going on").build();
        }
    
        // 从 repeatModelCache 获取到完整的回放结果记录
        RepeatModel rm = repeatModelCache.get(repeatId);
        // 如果取不到,返回错误
        if (rm == null) {
            return RepeaterResult.builder().success(false).message("invalid repeatId:" + repeatId).build();
        }
        // 返回完整的回放结果记录
        return RepeaterResult.builder().success(true).message("operate success").data(rm).build();
    }

    小结:

  • 从接口上看,调用顺序必须是 saveRecord -> repeat -> saveRepeat -> callback 。如果不对会导致后续接口调用失败。
  • 通过一个 repeatCached 的中间缓存,巧妙解决了回放还在进行中,查找回放结果时需要返回进行中这个场景。
  • 正常情况下 console 存储服务主要关注的是 Record 对象,缓存主要用的也是它。完整录制记录,由 RecordWrapper 负责。完整的回放结果记录,由 RepeatModel 负责。

实际使用

step 0 调整模式重新启动

上面分析了整个 console 服务的使用,主要提供的是存储服务、配置获取服务。很遗憾,里面并没有提供批量回放的接口,后续需要另行开发。

但上面终究只是从源码的推测,不实际跑下怎么知道是不是真的是这样呢?

根据官方的  用户使用手册 只需要把 ~/.sandbox-module/cfg/repeater.properties 里面的 repeat.standalone.mode 的值,从 true 改为 false 即可改为用 console 进行存储和配置获取。

同时,console 的一些配置项也要对应调整下,否则端口号和 repeater 的对不上,repeater-config 不正确,也会出问题

具体步骤:

1、杀掉原来的进程,关闭应用
2、修改  sandbox-module/cfg/repeater.properties 的值,repeat.standalone.mode 改为 false

[root@k8s-worker27-65 bin]# pwd
/root/work/traffic/wubo/jvm-sandbox-repeater/bin
[root@k8s-worker27-65 bin]# cat repeater.properties 
# 录制消息投递地址
broadcaster.record.url=http://127.0.0.1:8001/facade/api/record/save

# 回放结果投递地址
broadcaster.repeat.url=http://127.0.0.1:8001/facade/api/repeat/save

# 回放消息取数据地址
repeat.record.url=http://127.0.0.1:8001/facade/api/record/%s/%s

# 配置文件拉取地址
repeat.config.url=http://127.0.0.1:8001/facade/api/config/%s/%s

# 心跳上报配置
repeat.heartbeat.url=http://127.0.0.1:8001/module/report.json

# 是否开启脱机工作模式
repeat.standalone.mode=false

# 是否开启spring advice拦截
repeat.spring.advice.switch=false;

3.修改:repeater-console/repeater-console-start/src/main/resources/application.properties 数据库地址 

[root@k8s-worker27-65 jvm-sandbox-repeater]# cat  repeater-console/repeater-console-start/src/main/resources/application.properties 
spring.application.name=repeater-server
server.port=8001
mybatis.type-aliases-package=com.alibaba.repeater.console.dal.model
# 本地mysql数据源测试
spring.datasource.url=jdbc:mysql://192.168.1.65:3306/repeater?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456aA
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none

# 使用本地数据源进行测试
console.use.localCache =false
# 示例回放地址(工程使用需要维护repeater插件的ip:port替换,指定ip发起回放)
repeat.repeat.url=http://%s:%s/sandbox/default/module/http/repeater/repeat
# 示例配置地址(工程使用需要维护repeater插件的ip:port替换,指定ip发起回放)
repeat.config.url=http://%s:%s/sandbox/default/module/http/repeater/pushConfig
# 示例重载地址(工程使用需要维护repeater插件的ip:port替换,指定ip发起回放)
repeat.reload.url=http://%s:%s/sandbox/default/module/http/repeater/reload

# velocity
spring.velocity.cache= false
spring.velocity.charset=UTF-8
spring.velocity.check-template-location=true
spring.velocity.content-type=text/html
spring.velocity.enabled=true
spring.velocity.resource-loader-path=classpath:/velocity/templates
spring.velocity.prefix=/velocity/templates/
spring.velocity.toolboxConfigLocation=/velocity/toolbox.xml
spring.velocity.suffix=.vm

4. 创建数据库:

CREATE DATABASE IF NOT EXISTS repeater
    DEFAULT CHARSET utf8
    COLLATE utf8_general_ci;
DROP TABLE IF EXISTS record;
CREATE TABLE record
(
    id             BIGINT(20)    NOT NULL AUTO_INCREMENT PRIMARY KEY
        COMMENT '主键',
    gmt_create     DATETIME      NOT NULL
        COMMENT '创建时间',
    gmt_record     DATETIME      NOT NULL
        comment '录制时间',
    app_name       VARCHAR(255)  NOT NULL
        COMMENT '应用名',
    environment    VARCHAR(255)  NOT NULL
        COMMENT '环境信息',
    host           VARCHAR(36)   NOT NULL
        COMMENT '机器IP',
    trace_id       VARCHAR(32)   NOT NULL
        COMMENT '链路追踪ID',
    entrance_desc  VARCHAR(2000) NOT NULL
        COMMENT '链路追踪ID',
    wrapper_record LONGTEXT      NOT NULL
        COMMENT '记录序列化信息',
    request        LONGTEXT      NOT NULL
        COMMENT '请求参数JSON',
    response       LONGTEXT      NOT NULL
        COMMENT '返回值JSON'
)
    ENGINE = InnoDB
    COMMENT = '录制信息'
    DEFAULT CHARSET = utf8
    AUTO_INCREMENT = 1;

DROP TABLE IF EXISTS replay;
CREATE TABLE replay
(
    id              BIGINT(20)   NOT NULL AUTO_INCREMENT PRIMARY KEY
        COMMENT '主键',
    gmt_create      DATETIME     NOT NULL
        COMMENT '创建时间',
    gmt_modified    DATETIME     NOT NULL
        comment '修改时间',
    app_name        VARCHAR(255) NOT NULL
        COMMENT '应用名',
    environment     VARCHAR(255) NOT NULL
        COMMENT '环境信息',
    ip              VARCHAR(36)  NOT NULL
        COMMENT '机器IP',
    repeat_id       VARCHAR(32)  NOT NULL
        COMMENT '回放ID',
    status          TINYINT      NOT NULL
        COMMENT '回放状态',
    trace_id        VARCHAR(32)
        COMMENT '链路追踪ID',
    cost            BIGINT(20)
        COMMENT '回放耗时',
    diff_result     LONGTEXT
        COMMENT 'diff结果',
    response        LONGTEXT
        COMMENT '回放结果',
    mock_invocation LONGTEXT
        COMMENT 'mock过程',
    success         BIT
        COMMENT '是否回放成功',
    record_id       BIGINT(20)
        COMMENT '外键'

)
    ENGINE = InnoDB
    COMMENT = '回放信息'
    DEFAULT CHARSET = utf8
    AUTO_INCREMENT = 1;


DROP TABLE IF EXISTS module_info;
CREATE TABLE module_info
(
    id           BIGINT(20)   NOT NULL AUTO_INCREMENT PRIMARY KEY
        COMMENT '主键',
    gmt_create   DATETIME     NOT NULL
        COMMENT '创建时间',
    gmt_modified DATETIME     NOT NULL
        comment '修改时间',
    app_name     VARCHAR(255) NOT NULL
        COMMENT '应用名',
    environment  VARCHAR(255) NOT NULL
        COMMENT '环境信息',
    ip           VARCHAR(36)  NOT NULL
        COMMENT '机器IP',
    port         VARCHAR(12)  NOT NULL
        COMMENT '链路追踪ID',
    version      VARCHAR(128) NOT NULL
        COMMENT '模块版本号',
    status       VARCHAR(36)  NOT NULL
        COMMENT '模块状态'
)
    ENGINE = InnoDB
    COMMENT = '在线模块信息'
    DEFAULT CHARSET = utf8
    AUTO_INCREMENT = 1;


DROP TABLE IF EXISTS module_config;
CREATE TABLE module_config
(
    id           BIGINT(20)   NOT NULL AUTO_INCREMENT PRIMARY KEY
        COMMENT '主键',
    gmt_create   DATETIME     NOT NULL
        COMMENT '创建时间',
    gmt_modified DATETIME     NOT NULL
        comment '录制时间',
    app_name     VARCHAR(255) NOT NULL
        COMMENT '应用名',
    environment  VARCHAR(255) NOT NULL
        COMMENT '环境信息',
    config       LONGTEXT     NOT NULL
        COMMENT '配置信息'
)
    ENGINE = InnoDB
    COMMENT = '模块配置信息'
    DEFAULT CHARSET = utf8
    AUTO_INCREMENT = 1;
 repeater-console/repeater-console-dal/src/main/resources/database.sql

5、修复官方仓库里 console 一些代码问题。

5.1 把 repeater-console/repeater-console-start/src/main/resources/velocity 下面的所有文件,查找 #parse("/blocks ,统一改替换为 #parse("blocks 。原有代码最前面带上 / 会导致引用找不到报错

5.2 修改 repeater-console/repeater-console-start/src/main/java/com/alibaba/repeater/console/start/controller/page/ReplayController.java 中的 return "/replay/detail"; ,改为 return "replay/detail"; ,去掉双引号里面第一个 /

[root@k8s-worker27-65 jvm-sandbox-repeater]# cat repeater-console/repeater-console-start/src/main/java/com/alibaba/repeater/console/start/controller/page/ReplayController.java |grep "replay/detail"
        //return "/replay/detail";
        return "replay/detail";

5.3 修改 repeater-console/repeater-console-start/src/main/java/com/alibaba/repeater/console/start/controller/test/RegressPageController.java 中的 return "/regress/index"; ,改为 return "regress/index";,去掉双引号里面第一个 /

[root@k8s-worker27-65 jvm-sandbox-repeater]# cat repeater-console/repeater-console-start/src/main/java/com/alibaba/repeater/console/start/controller/test/RegressPageController.java |grep "regress/index"
        //return "/regress/index";
        return "regress/index";

6. 编译安装:

[root@k8s-worker27-65 bin]# ./install-local.sh

然后修改sandbox 的日志输位置

[root@k8s-worker27-65 sandbox]# cat  /opt/data/fll/sandbox/cfg/sandbox-logback.xml 
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="10000">

    <appender name="SANDBOX-FILE-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/opt/data/fll/sandbox/logs/sandbox/sandbox.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>/opt/data/fll/sandbox/logs/sandbox/sandbox.log.%d{yyyy-MM-dd}</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %SANDBOX_NAMESPACE %-5level %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="SANDBOX-FILE-APPENDER"/>
    </root>

</configuration>

7.启动console

[root@k8s-worker27-65 sandbox-module]# pwd
/opt/data/fll/sandbox/sandbox-module
[root@k8s-worker27-65 sandbox-module]# nohup java -jar repeater-bootstrap.jar  &

浏览器访问:

http://192.168.1.65:8001/regress/index.htm

8.现在,借助界面来做一次录制回放吧。基本套路还是一样的:

8.1、在 console 增加配置,用于对接应用(注意这个和之前纯命令行有点不同,命令行是先完成第二步)
8.2、让 repeater 注入到被测应用,上报数据到 console
8.3、在 console 中操作,进行录制和回放

接下来,一步一步操作。

8.1、在 console 增加配置,用于对接应用

点击左侧的【配置管理】,添加如下配置:

应用名:unknown
环境:unknown

{
  "useTtl" : true,
  "degrade" : false,
  "exceptionThreshold" : 1000,
  "sampleRate" : 10000,
  "pluginsPath" : null,
  "httpEntrancePatterns" : [ "^/greeting.*$" ],
  "javaEntranceBehaviors" : [],
  "javaSubInvokeBehaviors" : [ {
    "classPattern" : "hello.GreetingController",
    "methodPatterns" : [ "greeting" ],
    "includeSubClasses" : false
  } ],
  "pluginIdentities" : [ "http", "java-entrance", "java-subInvoke", "mybatis", "ibatis" ],
  "repeatIdentities" : [ "java", "http" ]
}

点击【保存】,存下配置

8.2、让 repeater 注入到被测应用

[root@k8s-worker27-65 bin]# pwd;./sandbox.sh -p 12449 -P 12580
/opt/data/fll/sandbox/bin
                    NAMESPACE : default
                      VERSION : 1.3.3
                         MODE : ATTACH
                  SERVER_ADDR : 0.0.0.0
                  SERVER_PORT : 12580
               UNSAFE_SUPPORT : ENABLE
                 SANDBOX_HOME : /opt/data/fll/sandbox/bin/..
            SYSTEM_MODULE_LIB : /opt/data/fll/sandbox/bin/../module
              USER_MODULE_LIB : /opt/data/fll/sandbox/sandbox-module;~/.sandbox-module;
          SYSTEM_PROVIDER_LIB : /opt/data/fll/sandbox/bin/../provider
           EVENT_POOL_SUPPORT : DISABLE

sh ~/sandbox/bin/sandbox.sh -p `ps -ef | grep "target/gs-rest-service-0.1.0.jar" | grep -v grep | awk '{print $2}'` -P 12580

然后进入 console 的【在线模块】,应该能看到增加了当前这个被测应用的心跳记录:

8.3、开始录制。给这个被测应用输送一些流量

# 手动发出2条请求,也可以在浏览器中出发

[root@k8s-worker27-65 bin]# curl -s 'http://localhost:8080/greeting'
{"id":60,"content":"Hello, World!"}您在 /var/spool/mail/root 中有新邮件
[root@k8s-worker27-65 bin]# curl -s 'http://localhost:8080/greeting?name=wubo1'
{"id":61,"content":"Hello, wubo1!"}

然后打开 console 的【在线流量】,能看到刚发出的两条请求已经录制下来了:

数据库:

8.4、回放请求。直接点击第一行末尾的回放按钮,进行回放:

详情:

回放:

然后,就可以看到回放结果了。稍等几秒后刷新下回放结果界面,就能看到执行结果

请求参数:

返回结果 

子调用:

总结

官方的文档还是一如既往的少,代码里面也有点坑(对 velocity 不熟悉,上面的代码只是按自己理解改的,如果有更正确的修改姿势欢迎分享),界面和技术栈都用的比较小众和比较久远的的(spring-boot 17 年已经去掉对 velocity 模板引擎的支持了)。 而且一个批量回放功能还是只有按钮实际没做的。。。

不过也算是给到大家一个真正示例控制台该有的样子,把需要的元素和界面设计都基本给出了。如果想要开箱即用,对 http 接口进行简单的录制回放,可以使用这个带界面的 console 来试用一下。

附录:过程中的报错及解决

1、注入 repeater 到被测应用后,console 报错:

39333 --- [nio-8001-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
    at com.alibaba.repeater.console.start.controller.api.ConfigFacadeApi.getConfig(ConfigFacadeApi.java:34) ~[classes!/:na]
    at sun.reflect.GeneratedMethodAccessor77.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]

原因:上报心跳包后,appName 和 environment 和配置对不上。
解决:请确认有至少一个配置,appName 和 environment 都是 unknown

2、报错 org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource '/blocks/pager.vm'] with root cause ,且界面打不开

原因:没有按照前面所述修改 console 源码,导致引用其他模板的部分根目录不正确。
解决:按照前面描述,把 #parse("/blocks ,统一改替换为 #parse("blocks 即可。

Michaelwubo
关注 关注
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
grep console颜色配置_通用流量录制回放工具 jvm-sandbox-repeater 尝鲜repeater-console
weixin_42477505的博客
12-27 598
为了避免文章过长,此文章单独记录 repeater-console 部分的使用。对于 jvm-sandbox-repeater 普通用法的尝鲜记录,请参照 通用流量录制回放工具 jvm-sandbox-repeater 尝鲜记录repeater-console 简介官方的说明:jvm-sandbox-repeater 仅仅提供了录制回放的能力,如果需要完成业务回归、实时监控、压测等平台,后面须要有...
流量录制回放工具jvm-sandbox-repeater入门——服务部署
Trouvailless的博客
05-05 2431
趋于当前技术不断更新、产品功能多元化之下,流量回放的热度也是越来越高。 在前一段时间,测试团队也提到阿里开源的流量回放工具jvm-sandbox-repeater 我个人就先尝试一下,期间还是遇到一些问题,通过咨询和度娘的帮助下,整体功能的使用流程是跑通了。 jvm-sandbox-repeater简介 在聊之前,我们先来了解下jvm-sandbox-repeater,其实在官网上也有详细的介绍,这里就直接搬官网的吧 jvm-sandbox-repeaterJVM-Sandbox生态体系下的重.
流量录制回放工具在自动化测试应用探索
主要分享测试的学习资源,帮助快速了解测试行业,帮助想转行、进阶、小白成长为高级测试工程师。
04-12 2388
流量录制回放技术可以快速将应用运行过程中产生的接口流量按照一定的过滤规则进行录制。录制的内容包括接口的请求报文以及响应报文,在录制过程中存储的报文可以随时在指定的环境上回放流量录制回放场景,既可以应用在功能测试阶段,实现回归验证,亦可以应用在版本验证阶段,实现自动交易回归。流量录制回放技术,在简化测试案例编写、提高测试效率、增强测试场景覆盖率等方面,具有重要意义。图1 流量录制回放过程。
推荐一款流量录制回放工具jvm-sandbox-repeater
最新发布
测试开发技术
09-06 1041
是一个基于采用Java来实现的流量录制回放工具,或者可以理解为它是一个基于Java虚拟机的插件,可以直接运行中JVM中,无需对目标应用程序进行任何修改。它可以在运行时自动拦截和记录 Java 应用程序的网络请求和响应。它是利用的字节码增强技术,对Java应用进行无侵入式的流量录制和回放。这意味着通过使用,我们可以在不修改源代码的情况下,即可轻松实现流量的录制和回放功能。总的来说,是一款功能强大、易于使用的流量录制回放工具,可以帮助开发人员或测试人员轻松地实现对网络请求的录制和回放。通过。
【干货】流量录制回放工具JVM-sandbox-repeater
测试开发技术
07-23 1592
【温馨提示】由于公众号更改了推送规则,不再按照时间顺序排列,如果不想错过测试开发技术精心准备的的干货文章还有精彩的课程,请将测试开发技术设为“星标☆”,看完文章在文尾处点亮“在看”!大家好,我是狂师!在软件开发和测试过程中,我们经常会遇到需要对网络请求进行录制和回放的需求,以便进行调试、测试和分析。为了模拟真实的用户请求,我们通常会使用各种流量录制回放工具来记录并重放网络请求。其中,jvm-san...
网络流量回放工具
12-04
网内网络流量记录仪,并可以回放之前监控的网络流量数据。
流量回放工具:goreplay实战
主要分享测试的学习资源,帮助快速了解测试行业,帮助想转行、进阶、小白成长为高级测试工程师。
11-08 3910
相信做性能测试的小伙伴们一定听说过流量复制回放,没听说过也没关系,我们大都是在性能测试环境完成压测任务出具性能测试报告,但是实际生产环境的接口压力流量往往和我们在性能测试环境预估的情况不完全一致,甚至出现较大差异的情况,我们如何模拟出真实的压力流量,可以借助工具,例如早期用得较多的是tcpcopy,但其缺乏过滤HTTP层能力;推荐使用另外一款工具goreplay,简称gor,其特点如下:实现HTTP请求的录制以及回放,应用场景是在线上环境录制请求,然后在测试环境进行重放。
jvm-sandbox-repeater:基于JVM-Sandbox的Java服务器端记录和回放解决方案
05-13
是生态体系下的重要模块,它具备了JVM-Sandbox的所有特点,插件式设计便于快速适配各种中间件,封装请求录制/回放基础协议,也提供了通用可扩展的各种丰富API。 目标人群 - 面向测试开发工程师 线上有个用户请求一直...
基于jvm-sandbox-repeater重新开发的一款流量回放平台产品
03-17
Moonbox(月光宝盒)是JVM-Sandbox生态下的,基于jvm-sandbox-repeater重新开发的一款流量回放平台产品。在jvm-sandbox-repeater基础上提供了更加丰富功能,同时便于线上部署和使用,更多对比参考。 使用场景 你...
流量回放专题-jvm-sanbox-repeater
质量架构之路
03-22 1552
流量回放jvm-sanbox、jvm-sandbox-repeater
jvm-sandbox-repeater源码解析-配置管理
Viogs的专栏
09-20 818
通过代码最终,我们最后发现是在com.alibaba.jvm.sandbox.repater.plugin.http.HttpStandaloneListener#doBefore里,也就是真正的 切点通知逻辑做的, 在拦截http的每次请求后判断是否需要采集;我们去看代码com.alibaba.jvm.sandbox.repeater.plugin.java.JavaEntrancePlugin的实现,发现JavaEntrancePlugin重写了getEnhanceModels。
windows下流量回放工具
01-29
windows下流量回放工具,亲测可用
自动化测试流量录制回放
人生不怕起点低,就怕没追求
07-24 2083
相信大家对“关键字驱动”和“数据驱动”这两个名词都已经很熟悉了,但是还有一些小伙伴其实对怎么定义它们还有些误解。比如前面讲的,我们把测试脚本中的数据参数化出来,放在一个文件里,是否就代表它是数据驱动了?并不是。
okhttp3测试框架_流量回放框架jvmsandboxrepeater的实践
weixin_39605997的博客
12-22 498
一. 前言你是否和我一样遇到过以下的问题?1)服务重构,一堆接口需要回归,让人头疼2)每次迭代,都要花很多精力来进行回归测试3)线上bug,线下复现不了4)接口自动化用例写辛苦,维护更辛苦… …或者许你正在被这些问题困扰。你可能和我一样也尝试过一些流量回放工具来解决上述问题,但最终经历了从入门到放弃的无奈。现有大部分流量回放工具中都存在这样那样的限制,比如只支持GET接口、不能对子调用进...
MoonBox:一款超实用无需代码侵入的流量录制与回放平台
easylife206的专栏
12-28 1959
公众号关注「奇妙的 Linux 世界」设为「星标」,每天带你玩转 Linux !当前互联网服务现状是调用关系复杂,服务模块多,流量复杂,业务复杂,对于测试团队构造请求成本比较高,如果还按照传统的人工构造请求参数,相对目前复杂业务场景来说太单一了。今天给大家分享一个线上流量录制和流量回放平台 -- MoonBox。项目简介Moonbox(月光宝盒)是一个无侵入的线上流量录制和流量回放平台。简单...
JVM-Sandbox--测试技术中引流回放的应用
Gamble6
05-21 1688
这样,在Sandbox中就可以调用新的类和方法,从而实现Mock的功能。、故障模拟、动态日志、行链路获取等等工具,就算我开发完成了,这些工具底层实现原理相同,同时使用,要怎么消除这些工具之间的影响,怎么保证这些工具动态加载,怎么保证动态加载/卸载之后不会影响其他工具,怎么保证在工具有问题的时候,快速消除影响,代码还原。系统间的异常模拟可以使用的工具很多,可是系统内的异常模拟怎么办,加开关或是用AOP在开发系统中实现,好想开发一个更优雅的异常模拟工具,既能模拟系统间的异常,又能模拟系统内的异常。
测试流量回放-介绍
码农崛起
09-18 2380
对于流量回放,如果用户的期望是完全替代手工测试,我会给介绍我们能解决的场景问题和带来价值,如果用户觉得不改变他的期望,建议还是把这个用户作为长尾用户,换一个用户快速推广。只有产品的功能和用户的期望匹配,用户才觉得我们产品能带来价值,才能作为金种子用户帮助我们打磨产品,否则双方可能会基于场景不匹配有很多的沟通,平台方做了再多的努力,没有达到预期,业务方也会认为产品的成熟度不够,这样的用户推广成本较大,但是收益很低。在回放时,通过相同入参,比较线上环境和测试环境接口的出参,判断测试环境的接口代码逻辑是否异常。
接口测试系列——AutoDiff流量回放在集成测试中的实践应用
06-09 2211
1.1 迭代形态及组织变化迭代加快在当今的互联网行业的形态背景下,产品需求迭代都愈加地追求快速交付流动,大部分公司都采用精益产品研发流程。那么在汽车之家经销商bu内部,我们近2年的平均需求交付时间≈4天。而潜在的问题是,由于原自动化接口测试的开发/维护成本较高已追不上迭代,我们大部分的回归测试是被跳过的,更多要依赖开发人员水平+测试人员经验,所以一旦常规的节奏被打破(紧急需求插入、人员状态、人员变化等),回归缺失的问题就会暴露出来,而这种外来变化其实也越来越频繁。项目/人员变更随着公司内部布局的各种变化,也
写文章

热门文章

  • shell中的(),(()),{},=,==,[],[[]]几种语法用法 48179
  • 在 Shell 脚本中调用另一个 Shell 脚本的三种方式以及返回值问题 42249
  • 数据库多表查询之 where和INNER JOIN 40044
  • Docker中Dockerfile多From 指令存在的意义 38139
  • Linux服务器上监控网络带宽的18个常用命令和linux带宽流量监控查看工具 36694

分类专栏

  • devops 4篇
  • k8s 3篇
  • Linux文件系统 14篇
  • Linux网络协议栈 2篇
  • Linux内存管理
  • Linux进程和线程 1篇
  • Linux命令 5篇
  • Linux-debian-deb包管理与制作 2篇
  • Linux-服务器和性能优化 18篇
  • Linux 内核配置与安装 11篇
  • Linux-集群 2篇
  • java-开发环境的搭建 1篇
  • java-IO
  • java-socket
  • java-Collection
  • java-Thread/Runtable 27篇
  • java-Reflect
  • java-Object and Class
  • java-代码优化
  • java-struts2
  • java-spring 1篇
  • java-hirbnate
  • java-SSH搭建
  • java-MVC框架
  • java-常用类和函数
  • java-分布式开发
  • DataBase-增删改查
  • DataBase-存储过程、函数、触发器等
  • DataBase-性能优化
  • Mysql 4篇
  • Database-分布式存储
  • DataBase-管理
  • 中间件-weblogic
  • Android-开发环境的搭建
  • Android-UI
  • Android-生命周期
  • Android-多线程-组件通信
  • Android-后台服务
  • Android-数据存储于访问
  • Big DataBase
  • 云计算和云存储 2篇
  • IT杂文趣事
  • 互联网发展
  • 电商
  • 互联网金融
  • C语言常用函数 237篇
  • 虚拟化 9篇
  • java
  • 管理 1篇

最新评论

  • VMware ESXi和vCenter和vSphere关系是怎样的?

    m0_45132528: 博主讲的真详细,大赞

  • /etc/hostname和/etc/hosts区别

    神笔码农s: 问题123在哪?

  • RKE部署Rancher v2.5.8 HA高可用集群 以及常见错误解决

    秦拿希: 太复杂了,有没有简单点的

  • docker 中文乱码

    CSDN-Ada助手: 不知道 云原生入门 技能树是否可以帮到你:https://edu.csdn.net/skill/cloud_native?utm_source=AI_act_cloud_native

  • K8S学习之centos7系统下Kubeadm方式搭建k8s集群

    奕生辰诺: 详细安装步骤 https://rrge.github.io/2019/11/20/install-k8s/

最新文章

  • 如何将Git仓库中的文件打包成zip文件
  • GPU 算力与 CUDA 版本对应关系
  • docker 中文乱码
2024年11篇
2023年58篇
2022年82篇
2021年40篇
2020年94篇
2019年45篇
2018年192篇
2017年20篇
2016年163篇
2015年41篇
2014年296篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值

玻璃钢生产厂家商场美陈价格北京商场卡通玻璃钢雕塑郑州铸铜玻璃钢彩绘雕塑设计梅州玻璃钢卡通雕塑怎么样泰州五一商场美陈梅州仿真玻璃钢雕塑济源玻璃钢雕塑室内校园玻璃钢景观雕塑定做淮南街道玻璃钢花盆青海卡通玻璃钢雕塑公司玻璃钢雕塑专业定制厂家广西商场美陈公司玻璃钢橘子卡通公仔雕塑鞍山质量好玻璃钢雕塑生产厂家朔州玻璃钢雕塑厂家商场玻璃钢雕塑供应厂家玻璃钢雕塑头像制作温州玻璃钢造型雕塑上海玻璃钢雕塑心形花盆云浮玻璃钢动物雕塑价格合理西藏艺术雕塑玻璃钢和田气球商场美陈怎样收费商场亮化美陈装置黄冈玻璃钢雕塑制作厂家商场童装区域美陈方案玻璃钢佛像雕塑生产厂家周口商场国潮美陈陕西现代雕塑玻璃钢河北玻璃钢雕塑销售厂家平凉玻璃钢植物雕塑价格香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化