流量录制回放近几年已经越来越火。如阿里开放的 doom 、滴滴开源的 RDebug 。而公司随着业务的快速发展,回归测试的耗时越来越长,而且可以留给脚本维护的时间也并不多。因此也期望通过流量录制回放,提高回归测试的效率。
而刚好前几天看到阿里技术公众号推送的一篇 jvm-sandbox 的文章,里面有提到开源了其中做流量录制回放的 repeater 模块,所以尝鲜一下。
为了方便记录和阅读,目前已经调整为系列文章。导航地址如下:
通用流量录制回放工具 jvm-sandbox-repeater 尝鲜记录(本文)
通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 使用
通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (三)—— repeater plugin 开发
通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (四)——新版带界面 console 的使用
直接搬运官方文档里面的介绍吧
github 地址:https://github.com/alibaba/jvm-sandbox-repeater
简单的说,就是一个可以在不修改程序的情况下,进行一个服务 http、java、dubbo 入参及返回值的录制。也支持快速扩展 api ,实现自己的插件。
cd bin
./bootstrap.sh
等待 SpringBoot 应用启动完成 -> Started Application in 4.797 seconds (JVM running for 6.586)
curl -s 'http://127.0.0.1:8001/regress/slogan?Repeat-TraceId=127000000001156034386424510000ed'
执行结果如下:
访问链接时,repeater 插件通过 Repeat-TraceId=127000000001156034386424510000ed,唯一追踪到了这一次请求,后台服务返回了
JAVA是世界上最好的语言!
,repeater 把画面定格在了这一秒并将结果和 firstId 绑定
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 的示例项目当做实际项目使用。
项目地址: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 会自动递增。
接下来,按照官方的说明,进行操作:
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 ======
根据需要修改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
}
先到刚才 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
录制几个请求:
➜ 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 更新:官方已经更新回放文档啦,接着进行下去。
官方的说明:
模块暴露了回放接口,用于服务端发起远程回放,具体如下:
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 序列化。。。看起来就不是给我们这种命令行触发用的。先跳过。
从前面的 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 里面的 readme ,可以看到它也是有暴露接口供调用的。因此也试试。
结果看了下,里面提供的 standalone 和 mysql 两种数据存储方式,都不支持前面回放的存储方法(存在 ~/.sandbox-module/repeater-data/record
中)。还得调整录制方式才能进行回放。
7.16 更新:此回放方式已跑通。详细的记录,请参照:通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 使用
待补充
待补充
项目的源码目录如下:
$ 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 月放出,基本上覆盖了项目中最常用的几个中间件了。
虽然目前文档还不是非常完整,但看得出来是有在用心维护的,昨天周六也有更新了一版文档,补充了关于原理方面的说明。相信只要有足够的时间,会变得更完善的。
最后,非常感谢阿里能开源一个这么强大的组件,这样一些质量技术方面没法有太多资源投入的公司,也可以把流量录制回放搞起来了。