作者|张天博(搏天)
出品|阿里巴巴新零售淘系技术部
导读:随着业务的不断发展, 整个淘系的服务端已经有数千个应用,在淘宝已经有非常大的应用数量和变更次数的基础上, 对流量回放也有更高的要求。那么在不断尝试流量的录制与回放的过程中,我们遇到了什么问题?那么在不断尝试的过程中,我们遇到了什么问题?我们由从中得到了什么启示?流量录制回放又能给我们带来多少收益?本文将一一介绍。
开源地址
提前放出开源地址,本开源项目是一款 基于 JVM-Sandbox 开发的一款 Java 流量录制、回放通用解决方案,下文将会详细介绍,长按识别下方二维码,关注“淘宝技术”官方公众号,并在对话框回复“git”即可获得下载链接、了解更多详情。
为什么需要流量回放
淘宝网陪伴我们剁了已经有 15 年的手,在经过这么久的演变后, 整个淘系的服务端已经成为了有数千个应用, 每年变更数万次的大航母, 我们在面对这个航母时, 需要既能全方位了解系统的全盘数据,又能从小处着眼,观察 细节的精细代码运行状况。
并且, 面对全国以至于全世界物联网网民时,我们的测试用例写的肯定会有疏漏和不足, 我们也希望能从线上获 取到更加有意义的用例。
同时,我们整个淘宝已经有非常大的应用数量和变更次数了, 我们对流量回放也有更高的要求。
于是,我们开始了我们的流量回放研发之路。
技术选型
技术上选型我们使用了 JVM-Sandbox (以下简称 sandbox ), 其主要的特性与我们想满足的需求都是比较贴切的。
淘系技术质量开源项目「JVM-SANDBOX」在《MTSC 2019年度开源项目奖 花落淘系技术质量团队》一文中有提及,如果要获取开源地址可关注“淘宝技术”官方公众号,并在对话框回复“测试”即可获得下载链接、了解更多详情。
第一个流量录制与回放
首先,我们要找到第一个可录制的流量, 我们曾经尝试过比较简单的实现方案, 比如直接录制入口请求的地点, 然后通过 JSON 化后打 LOG , 回流数据, 进行数据存储后, 以泛化调用的方式进行回放。 这样实现起来固然简单 并且快捷, 但是这样有一个致命的问题, 那么就是我们丢失了大量的 class 信息, class 信息丢失对于比较重的参数、返回值执行是有相当大的风险的, 会导致无法无法反序列化, 导致回放失败, 也可能因为循环引用,需要进 行定点改造, 这样就非常麻烦了,做不到我们的 0 代码改造。
当我们意识到这个问题后,我们明确的分析了问题所在, 解决办法也比较明确了, 因为只有同样的 JVM 才会加载 出同样的 class 信息,所以我们必须要在同JVM对录制的参数、返回值进行 java 序列化/反序列化( hessian/kyro 等)进行录制, 在同机进行反序列化后进行同机回放, 保证原汁原味 的 class 信息, 不再会有无法反序列化的问题。
录制部分
通过 sandbox aop 到中间件层的 invoke 方法, 获取参数和返回值, 以 hessian 序列化成二进制,并通过消息的方式 发送至服务端进行存储。
回放部分
从存储的二进制的数据拉取到本机, 通过反序列化成为 JavaObject , 直接以本地代码 invoke 执行, 得到返回值后 进行对比,得到结果。
于是,我们第一个简单且稳定的录制回放就完成了, review 一下:
可以看到,我们这个方法可以解决读接口的行为,无法满足写接口的行为, 写坏数据了就会引起故障, 我们如果想完整的进行录制回放,那么我们必须要解决这个写接口回放的问题。
写接口的录制和回放
我们究竟要怎么测写接口?如果是要完整落库后删除,那么可以通过回溯 binlog 等方式尝试做到,但是成本极高, 我们选择退而求其次的方式:在回放时,只要参数和类型一样, 就将真正的写行为直接 mock 掉。其具体做法就是:
在这里,我们将所有的 JVM 内请求进行了分类, 以URI作为区分类型标准, 以URI为准进行回放的 mock 行为准 则, 那么我们的整体架构有了一点点改变:
录制部分
我们将所有的子链路都进行了录制, 每一个我们认定的子链路都以序列化的方式进行了存储,并且加入了 URI 和 index 来进行区分。
回放部分
回放的时候, 每当回放流量执行代码中遇到我们的子链路代码时,都从数据中拉取相同 URI 的数据进行反序列 化, 当参数一致时, 直接以录制时的结果进行返回, 返回录制的那一个子链路的反序列化的 object 。
在这里可以看到,我们还有一个 script engine , 这里是插入了一个 groovy 代码执行器,主要是为了当我们遇到诸 如时间 、动态配置等类型的数据时,参数不一致,导致了我们子链路请求不一致, 无法正确的进行对比参数回放,所以通过一段 groovy 代码进行录制参数和执行参数修改, 将值赋值成为同样的结果后, 动态数值就会被固定 下来,可以被整成回放了。
至此,我们的写操作也完整的进行了录制和回放, 也保证了数据安全。
不过,我们可以看出,当我们将线上流量的录制量提高以后,我们出错后的排查成本非常高, 比如同样一个接口 的 1 万个 case 错 2000 个, 这里需要排查起来就太困难了,我们需要更加完备的测试方式进行区分和组合我们的 case 。
执行范围与测试范围分析
我们在制作录制回放的同时,我们也做了基于接口链路的执行路径录制,可以很明确的分辨出每一个请求的执行 代码路径(类似 Jacoco ,不过 Jacoco 是基于整体的, 基于接口/线程级需要自己修改 Jacoco , 也不是很复杂), 基于这个路径,我们做了以下的事情:
- 进行了代码链路聚合,录制的 case 我们可以用执行路径的区别进行等价类划分,称为链路热度,同样的路 径,我们可以一目了然, 以同样的路径进行推荐就可以作为链路 case 推荐,大大减少重复的 case 。
- 代码路径在回放的时候也进行链路追踪,执行追踪后可以得到路径的diff,也同样的能感知到修改的代码是 否对结果有影响,是否链路发生改变, 可以得出变更的范围。
总体效果及展望
目前我们在运行的流量录制回放应用在例如大型架构迁移应用上,基础能力的回归基本可以做到 0 成本, 可以稳定 的进行回放。
经过过去一年的建设, 如本文开头说的,我们开源了这整套体系,不过,我们并不会止步于此, 基于JVM-sandbox 和 repeater ,我们更多可以涉足的领域,例如:压测数据生成、故障模拟、混沌工程等应用场景, 我们将一直向前, 为整个行业带来丰富多彩的能力。