作者:chengxiaojun
一、对比引擎产生背景
有赞作为一家 SaaS 公司,除了传统的微商城,还提供了零售、美业等产品解决方案。随着公司业务的快速发展,各业务系统也不断的进行着功能迭代或系统重构,如何保证这个过程中系统功能的正确性和服务的稳定性,是公司测试和开发人员需要面对的一个重要挑战。
目前有赞内部已经有一套机制来保证业务系统的质量,包括一些常规的自动化测试工具和人工测试。但常规的自动化测试工具需要准备大量测试数据,并需要编写各类测试脚本,不但成本高而且效率低。
另外随着业务的快速发展,系统和业务复杂度也在不断提升,需要回归验证的业务场景越来越多。通过编写用例的方式很难覆盖大部分场景,测试人员只能保证对一些核心场景的回归验证,无法实现更进一步把控业务系统质量的要求。
基于以上背景我们研发了有赞自动化服务回归验证平台 - 对比引擎(replay),通过它可以对服务接口进行自动化回归验证,可以大大降低测试成本和提升测试效率。
二、认识对比引擎
2.1 对比引擎是什么
对比引擎是一个高效的服务回归验证平台,它通过复制线上真实请求到预发环境执行,然后对比线上和预发响应,通过判断线上和预发请求响应结果来识别接口正确性,其中通过请求的响应结果来验证服务接口正确性主要基于这样的经验:
大部分业务迭代过程中对代码的修改最终会体现在接口返回值上,可以通过检测同样请求下接口返回值的差异来验证服务接口正确性
备注:有赞大部分业务系统是线上和预发环境共用一套底层基础服务,包括 DB、ES 和 MQ 等服务
2.2 对比引擎的优势
对比引擎通过复制线上真实流量去做自动化回归,很容易发现项目迭代及重构中带来的 bug,它同传统的服务回归验证工具及手段相比有如下优势:
- 使用线上真实流量对服务接口进行回归验证,接口覆盖更全面,测试质量也更有保证
- 自动化实时采集线上真实流量对接口进行验证,效率大大提升
- 可以快速验证接口返回新增或减少字段
- 发现一些隐藏的需要长期运行才能发现的问题
三、对比引擎设计与实现
3.1 一期设计与实现
- 对比引擎一期的设计目标是:
支持读接口重放
下面是对比引擎整体架构图
对比引擎主要包含以下几个组件:
- 客户端 SDK:用于拦截线上请求和响应,并将请求和响应组装后发送到 MQ
- 对比引擎服务端:整个对比引擎服务的核心,进行请求重放和结果比对,并负责存储不一致信息
- 对比引擎控制台:a)配置应用重放和比对参数 b)查看应用接口不一致信息
3.1.1 客户端 SDK
客户端 SDK 内部逻辑如下图虚线框所示,通过 AOP切面方式
实现请求(方法级别)的采集,之后将请求异步收集到 MQ 消息队列中
备注:目前只支持读接口的回归验证,写接口不支持主要是防止写接口重放导致出现脏数据,写接口的重放后在后面单独有一节进行介绍
下面是 SDK 的一些设计要点:
- 请求流量需要支持采样,防止线上请求流量过大造成对比引擎后端和服务预发环境压力过大
- 需支持请求蓄流重放,解决某些接口流量比较小在预发部署时没有流量的情况
- 通过异步方式采集请求,以避免对服务接口性能造成影响
3.1.2 对比引擎服务端
下面是对比引擎服务端结构图,主要包含 2 个处理逻辑:
- 请求重放:支持 dubbo 请求重放(使用 dubbo 泛化调用方式)和 rest 接口请求重放
- 结果比对:对预发和线上的请求响应结果进行比对,如果响应结果不一致将不一致结果存储 DB 和 KVStore 中
下面是服务端的一些设计要点:
- 要做好防误报,在对比引擎的早期使用过程中我们发现误报很频繁,如果不处理使用者就要花费大量的时间排查误报。根据我们的观察,发现大部分误报是因为请求重放期间数据变更(如下单导致的库存频繁变更)导致预发和线上响应不一致。这可以通过请求多次重放(重放过程会重新请求线上和预发环境)来避免误报
- 请求重放或对比失败后消息放到重试 topic,防止阻塞正常请求 topic
- 为了提升对比引擎服务端的比对能力,后端是使用线程池来执行请求重放(请求重放主要是 RPC 调用,属于 I/O 密集型操作)。另外考虑到各个服务接口的 RT 差别很大,我们对线程池进行划分,根据接口请求的 RT 分布情况将请求调度到不同的线程池上进行执行
- 请求响应结果比对需要考虑支持如下自定义配置,已满足各种业务需求,比如:
- 忽略特定字段比对
- 列表支持忽略顺序
3.2 二期设计(开发中)
- 对比引擎二期的设计目标是:
支持写接口重放
3.2.1 支持写接口重放的几个关键问题
要支持写接口的重放,首先需要考虑如下几个问题:
- 如何判定服务写接口的正确性?
最简单直观的判定方法是:对于同一个接口,相同的请求参数下响应结果一致则认为是正确。
但是只根据响应结果就能判定接口正确性么?比如有的接口响应为PlainResult<Void>类型,可能内部逻辑是有变更,但是无法反映在响应结果上。
我们可以换个角度来思考,理论上在应用数据上下文一致的情况下,2次同样的写操作产生的数据变更应该是一样的,通过比对数据变更情况就能判断服务写接口的正确性。如果相同则认为逻辑没问题,如果不相同则认为逻辑有问题。这里有2个关键点:
- 需要能感知所有数据变更(变更条件也算,比如变更SQL,相同的变更SQL我们认为产生的数据变更是一样的)
- 需要能构造相同的应用上下文(包括数据等信息)
- 如何保证重放的写请求不会导致业务出现脏数据?
要支持写接口请求重放,必须要保证业务不会出现脏数据,否则会对业务产生不可估量的影响
3.2.2 支持写接口重放设计
核心思路:
- 请求录制阶段:记录接口调用过程中所有和外部组件的交互信息(包括请求、响应等关键信息),作为请求的上下文
- 请求重放阶段:mock 接口调用过程中所有和外部组件的交互行为,根据请求参数去匹配请求上下文中的响应信息,mock 请求的响应结果。这里 mock 接口调用过程中所有和外部组件的交互行为,主要是考虑到让业务方去区分外部组件是读或写操作本身就有难度,如果有遗漏就可能会产生大量脏数据,风险太高
其中和第三方外部组件的交互入口需要框架组帮忙配合梳理,好在有赞框架组之前在做调用链路追踪系统时已对所有第三方组件进行过埋点,只需暴露第三方组件交互入口即可实现记录请求参数和响应信息的拦截或 mock
3.2.2.1 写请求录制
下面是写请求录制阶段流程图重要组件说明
- ReplayClientAspect: Replay Client SDK 的核心组件,负责请求录制,对于写接口需要记录请求上下文信息,对于重放请求不会进行再次录制
- Framework: 第三方框架的核心组件,负责记录写请求调用阶段和所有第三方组件或接口的交互上下文信息(包括请求、响应等关键信息)
流程说明
- ①:接口调用达到 ReplayClientAspect,切面记录请求上下文,并初始化第三方框架交互上下文
- ②:业务逻辑执行过程中和第三方交互时,记录交互请求
- ③:业务逻辑执行过程中和第三方交互完成后,记录交互详情信息,并录入到整个请求的第三方框架交互上下文中
- ④:执行完业务逻辑,记录并保存请求信息(异步方式)
3.2.2.2 写请求重放
下面是写请求重放阶段流程图重要组件说明
- ReplayClientAspect: 同上,这里需要补充的是对于重放写请求,需要加载请求上下文信息(包括第三方框架交互上下文)
- Framework: 第三方框架的核心组件,如果发现是重放写请求,则负责 mock 写请求调用阶段和所有第三方组件或接口的交互行为
备注:
- 重放阶段框架上下文信息的加载由 Replay Client SDK 触发进行加载
- 对于重放写请求 ReplayClientAspect 会打上特殊标识,Framework 能识别出重放写请求
流程说明
- ①:接口调用达到 ReplayClientAspect,加载第三方框架交互上下文
- ②:业务逻辑执行过程中和第三方交互时,需要从 Replay Client SDK 的第三方框架交互上下文信息中匹配到接口的响应信息
- ③:业务逻辑执行过程中和第三方交互是,mock 交互行为
- ④:执行完业务逻辑,返回响应信息
其中 Replay Client SDK 和 Framework 的交互点有 2 个:
- 写请求录制阶段 Replay Client SDK 从 Framework 获取上下文信息
- 写请求重放阶段 Replay Client SDK 加载上下文信息
需要注意的是:放了防止框架 bug 或者一些其他不可控的因素对线上系统造成影响,如误操作数据等,方案需要考虑兜底手段。这里想到的是对重放请求加压测请求标,这样即使有问题也只是影响的影子库数据,不会对线上业务数据造成影响
四、应用接入和效果展示
4.1 应用接入
应用只需在接口上添加 @Replay
注解就能实现接入
@Replay
@Override
public ListResult<ItemSkuModel> listBySkuCodes(ListBySkuCodesParam param) {
//这里略去接口实现代码
...
}
4.2 设置请求采集和重放配置
通过控制台设置需要采集请求的接口、接口的请求采样 QPS 及重放配置信息
另外通过控制台可以设置对比相关配置,比如忽略比对的字段(有些字段是随着时间变化场景),忽略列表顺序(返回顺序无保证场景)
4.3 效果展示
五、总结和展望
本文主要介绍了对比引擎的研发背景及实现原理,作为一种自动化服务回归验证工具,对比引擎极大的提升了业务的场景覆盖率和回归验证效率,对于保证线上服务稳定起到了良好的作用,目前公司内部大部分业务(包括商品、库存、营销、会员等)都已接入。
后续一个重点是完善用例库管理功能,包括识别各种请求模板、用例采集功能、用例导出以及跨环境的回归验证(线上引流到测试环境),以更好的完善核心场景测试用例并提升测试效率。
欢迎关注我们的公众号