接口测试 通用流量录制回放工具 jvm-sandbox-repeater 尝鲜记录 (0716 跑通基于 console 的录制回放)

陈恒捷 for PPmoney · July 07, 2019 · Last by 陈恒捷 replied at August 06, 2019 · 5123 hits
本帖已被设为精华帖!

背景

流量录制回放近几年已经越来越火。如阿里开放的 doom 、滴滴开源的 RDebug 。而公司随着业务的快速发展,回归测试的耗时越来越长,而且可以留给脚本维护的时间也并不多。因此也期望通过流量录制回放,提高回归测试的效率。

而刚好前几天看到阿里技术公众号推送的一篇 jvm-sandbox 的文章,里面有提到开源了其中做流量录制回放的 repeater 模块,所以尝鲜一下。

为了方便记录和阅读,目前已经调整为系列文章。导航地址如下:

通用流量录制回放工具 jvm-sandbox-repeater 尝鲜记录(本文)
通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 使用
通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (三)—— repeater plugin 开发

jvm-sandbox-repeater 简介

直接搬运官方文档里面的介绍吧

image_1df2dcu799j9pif16k3kje1s1j9.png-187.1kB
image_1df2ddd0jf2sk7rvjlqkduavm.png-61.8kB

github 地址:https://github.com/alibaba/jvm-sandbox-repeater

简单的说,就是一个可以在不修改程序的情况下,进行一个服务http、java、dubbo入参及返回值的录制。也支持快速扩展 api ,实现自己的插件。

standalone 快速开始

step0 安装sandbox/启动bootstrap

cd bin
./bootstrap.sh

等待SpringBoot应用启动完成 -> Started Application in 4.797 seconds (JVM running for 6.586)

step1 开始录制

curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=127000000001156034386424510000ed'

执行结果如下:

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

step2 开始回放

curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=127000000001156034386424510000ed'

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

完整执行结果如下:

  bin git:(master) curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>%

bin git:(master) curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId-X=127000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>%

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

测试一下:

# 录制一个 128 开头的 traceId ,返回结果是 java
bin git:(master) curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=128000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">JAVA是世界上最好的语言!</h1>%
# 录制一个 129 开头的 traceId ,返回结果是 Python
bin git:(master) curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=129000000001156034386424510000ed'
<h1 align="center" style="color:red;margin-top:300px">Python是世界上最好的语言!</h1>%
# 回放前面 128 的流量
bin git:(master) 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>%
# 回放前面 129 的流量
bin git:(master) 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>%

看来确实是有效的。

录制目标服务

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

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

  • restful-api

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

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

程序本身功能:当请求 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

正常日志输出:

======  begin to install sandbox and repeater module       ======
====== step 0 begin to download sandbox package ======
====== step 1 begin to download repeater module package ======
====== install finished ======

step1 修改repeater-config.json,启用拦截点和插件信息

根据需要修改repeater-config.json配置文件,具体配置含义参见:RepeaterConfig.java

repeater-config.json 配置文件,位置是 ~/.sandbox-module/cfg/repeater-config.json

具体的配置含义,官方提供的链接相对路径有问题,无法跳转。可以直接看这个链接:RepeaterConfig.java

此处根据这个被测项目的需要,进行了调整:

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

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

step2 attach sandbox到目标进程

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

# 在示例项目 clone 后的根目录中运行
cd complete
mvn install && java -jar target/*.jar

# 查看进程 id
ps aux | grep target/gs-rest-service-0.1.0.jar
hengjiechen 7737 0.0 0.0 4267932 616 s007 R+ 10:23AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn rest-service
hengjiechen 7306 0.0 3.2 7935464 269884 s000 S+ 10:23AM 0:14.84 /usr/bin/java -jar target/gs-rest-service-0.1.0.jar

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

cd ~/sandbox/bin

# 假设目标JVM进程号为'7306' -P 是设定 jvm-sandbox 的端口号,后面回放需要用到
./sandbox.sh -p 7306 -P 12580

小技巧:上述的找进程 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

控制台输出:

          NAMESPACE : default
VERSION : 1.2.1
MODE : ATTACH
SERVER_ADDR : account.jetbrains.com
SERVER_PORT : 53866
UNSAFE_SUPPORT : ENABLE
SANDBOX_HOME : /Users/hengjiechen/sandbox/bin/..
SYSTEM_MODULE_LIB : /Users/hengjiechen/sandbox/bin/../module
USER_MODULE_LIB : /Users/hengjiechen/sandbox/sandbox-module;~/.sandbox-module;
SYSTEM_PROVIDER_LIB : /Users/hengjiechen/sandbox/bin/../provider
EVENT_POOL_SUPPORT : DISABLE

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

$ tail -200f ~/logs/sandbox/repeater/repeater.log

...
2019-07-07 10:24:14 INFO initializing logback success. file=/Users/hengjiechen/.sandbox-module/cfg/repeater-logback.xml;
2019-07-07 10:24:14 INFO module on loaded,id=repeater,version=1.0.0,mode=ATTACH
2019-07-07 10:24:14 INFO onActive
2019-07-07 10:24:14 INFO pull repeater config success,config=com.alibaba.jvm.sandbox.repeater.plugin.domain.RepeaterConfig@4dddeb36
2019-07-07 10:24:15 INFO enable plugin http success
2019-07-07 10:24:15 INFO add watcher success,type=http,watcherId=1000
2019-07-07 10:24:16 INFO register event bus success in repeat-register

step3 开始录制和回放

录制几个请求:

  complete git:(master)  curl -s 'http://localhost:8080/greeting?name=User'
{"id":1,"content":"Hello, User!"}%
complete git:(master) curl -s 'http://localhost:8080/greeting?name=User2'
{"id":2,"content":"Hello, User2!"}%
complete git:(master) curl -s 'http://localhost:8080/greeting?name=User3'
{"id":3,"content":"Hello, User3!"}%
complete git:(master) curl -s 'http://localhost:8080/greeting'
{"id":4,"content":"Hello, World!"}%

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

...
2019-07-09 16:31:20 INFO broadcast success,traceId=192168015059156266108005510001ed,resp=success
2019-07-09 16:31:26 INFO broadcast success,traceId=192168015059156266108604210002ed,resp=success
2019-07-09 16:31:31 INFO broadcast success,traceId=192168015059156266109135010003ed,resp=success
2019-07-09 16:31:36 INFO broadcast success,traceId=192168015059156266109622310004ed,resp=success

好了,试试回放。

怎么回放?文档完全没提到,一脸懵逼。。。等官方文档更新把。

20190709更新:官方已经更新回放文档啦,接着进行下去。

方式一:利用模块暴露的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 传递的,必须录制和回放都用一样的参数

# 第一种写法:
$ curl -s 'http://localhost:8080/greeting' -H "Repeat-TraceId-X:192168015059156266109622310004ed"
{"id":4,"content":"Hello, World!"}%
$ curl -s 'http://localhost:8080/greeting?name=User3' -H "Repeat-TraceId-X:192168015059156266109135010003ed"
{"id":3,"content":"Hello, User3!"}%

# 第二种写法:
$ curl -s 'http://localhost:8080/greeting?Repeat-TraceId-X=192168015059156266109622310004ed'
{"id":4,"content":"Hello, World!"}%

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

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

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

2019-07-10 17:19:25 INFO  initializing logback success. file=/Users/chenhengjie/.sandbox-module/cfg/repeater-logback.xml;
2019-07-10 17:19:25 INFO module on loaded,id=repeater,version=1.0.0,mode=ATTACH
2019-07-10 17:19:25 INFO onActive
2019-07-10 17:19:25 INFO pull repeater config success,config=com.alibaba.jvm.sandbox.repeater.plugin.domain.RepeaterConfig@61e09144
2019-07-10 17:19:25 INFO enable plugin http success
2019-07-10 17:19:26 INFO add watcher success,type=http,watcherId=1000
2019-07-10 17:19:26 INFO enable plugin java-subInvoke success
2019-07-10 17:19:26 INFO add watcher success,type=java,watcherId=1003
2019-07-10 17:19:27 INFO register event bus success in repeat-register
2019-07-10 17:19:31 INFO broadcast success,traceId=192168015059156275037036610001ed,resp=success
2019-07-10 17:19:32 INFO broadcast success,traceId=192168015059156275037271710002ed,resp=success
2019-07-10 17:19:41 INFO find target invocation by PARAMETER_MATCH,identity=java://hello.GreetingController/greeting~S,invocation=com.alibaba.jvm.sandbox.repeater.plugin.domain.Invocation@3af687c7
2019-07-10 17:19:52 INFO find target invocation by PARAMETER_MATCH,identity=java://hello.GreetingController/greeting~S,invocation=com.alibaba.jvm.sandbox.repeater.plugin.domain.Invocation@6b6f0533

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

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

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

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

7.16更新:此回放方式已跑通。详细的记录,请参照:通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 使用

  • mybatis + redis

待补充

  • mq

待补充

源码结构简析

项目的源码目录如下:

$ tree -L 2 | grep -v iml
.
├── LICENSE
├── Readme.md
├── bin
│   ├── bootstrap.sh
│   ├── health.sh
│   ├── install-local.sh
│   ├── install-repeater.sh
│   ├── package.sh
│   ├── repeater-config.json
│   ├── repeater-logback.xml
│   └── repeater.properties
├── docs
│   ├── plugin-development.md
│   ├── slogan-demo.md
│   └── user-guide-cn.md
├── hessian-lite
│   ├── pom.xml
│   └── src
├── pom.xml
├── repeater-client
│   ├── pom.xml
│   └── src
├── repeater-console
│   ├── Readme.md
│   ├── pom.xml
│   ├── repeater-console-common
│   ├── repeater-console-dal
│   ├── repeater-console-service
│   ├── repeater-console-start
├── repeater-module
│   ├── pom.xml
│   └── src
├── repeater-plugin-api
│   ├── pom.xml
│   └── src
├── repeater-plugin-core
│   ├── pom.xml
│   └── src
├── repeater-plugins
│   ├── dubbo-plugin
│   ├── http-plugin
│   ├── ibatis-plugin
│   ├── java-plugin
│   ├── mybatis-plugin
│   ├── pom.xml
│   ├── redis-plugin
└── travis.sh

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

吐槽

20190709更新:之前提到的3个吐槽点官方都第一时间修复了,效率很高。

这次补充两个点:

1、repeater-console 的说明还是太少,虽然通过阅读源码大致了解了它的功能,但不如有文档方便,对新手不大友好。建议补充对应的说明。

2、回放提供的2个方式都不是太友好,方式二比较简单,但不支持批量,不适合项目使用。方式一基本上只能通过编程方式进行,无法直接通过接口进行。

建议提供一个命令行工具,可以直接通过命令行参数自动组装和序列化输出 _data 参数,便于用最简单的方式调用回放功能。

小结

这个开源项目是 7月4日 出来的,由于是直接在 jvm 层控制,从原理上是通用性、扩展性最强的。而且目前也提供了 mybatis、http、dubbo 、Java 等插件,redis 预计7月放出,基本上覆盖了项目中最常用的几个中间件了。

虽然目前文档还不是非常完整,但看得出来是有在用心维护的,昨天周六也有更新了一版文档,补充了关于原理方面的说明。相信只要有足够的时间,会变得更完善的。

最后,非常感谢阿里能开源一个这么强大的组件,这样一些质量技术方面没法有太多资源投入的公司,也可以把流量录制回放搞起来了。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 16 条回复 时间 点赞

马克,本社区说明最详细最接地气的一篇流量回放的文章。
期待楼主持续更新,楼主吃螃蟹,我吃蟹小爪😂

感谢大佬尝鲜,期待更新!!

赞。效率真高!周末没闲着,这么快就出来这么详细的文章了。

有尝试通过的大神吗?

hello 回复

你这个尝试出效果了吗?

yuzijinrxh 回复

没有,我在等陈大师出后续呢。

026 将本帖设为了精华贴 12 Jul 11:12

大佬能给解释下javaSubInvokeBehaviors,javaEntranceBehaviors这俩配置项的作用嘛,官方文档看的不是太懂。。。

Author only
oh_test 回复

个人理解,javaSubInvokeBehaviors对应子调用,回放时会被 mock 掉(前提是回放时给到的入参和录制时一致),javaEntranceBehaviors是入口调用,回放时会被作为输入。

举个例子,a 接口对应的 controller 方法是 A,而 A 的实现里面有调用另一个系统的接口,方法是 B 。录制回放主要测试的是 A 方法的逻辑,不想依赖另一个系统。
此时,应该配置 javaEntranceBehaviors 为 A ,javaSubInvokeBehaviors 为 B 。

这样回放时,A这个入口调用会被原封不动地发给应用进行回放,而 B 这个调用会被 mock 掉,应用实际不会发请求给另一个系统。

GCpigsic 回复

执行 cd complete mvn install && java -jar target/*.jar 之后,你是新开窗口执行 ps aux | grep target/gs-rest-service-0.1.0.jar 的吗?

从你的描述上看,这个 jar 包在 attach 的时候是没有在运行的。

Author only
GCpigsic 回复

刚找了台 windows 机器结合 git bash 试了下,貌似 ps 不会显示所有进程。可能是 windows 下不支持。

可以试试直接改为用控制台找到的 pid ,继续进行后续的 attach 步骤。

陈恒捷 回复

我试过直接绑定 控制台找到的 pid,继续attach ,就会出现报这样的错

😭 😭

GCpigsic 回复

好吧。。。

建议弄台 linux 虚拟机,或者给官方提个 issue 。这块比较深入 windows 和 linux 的差异了,我也不大知道怎么解决比较好。

陈恒捷 回复

感谢你!!不得不说,文章真的棒很详细!!!

GCpigsic 回复

感谢支持~

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up