《流量回放技术揭秘》

引言

本文主要介绍一种流量回放技术,通过采集底层流量数据,使用创新的Mock方式将数据应用在功能回归测试场景,更好的保障业务迭代质量。

应用场景

  • 日常上线回归测试:确保每次上线核心流程不受影响,可以接入pipeline流水线。
  • 开发重构类需求:微服务拆分、框架升级、组件升级等。

应用特性

  • 线上流量测试:更真实、更全面、测试用例维护成本更低。
  • 支持写接口回放:所有下游调用都会被mock返回,不会真实写入数据库,不会污染应用数据。

一、背景

1.1、背景

需求改动难以评估影响面,这是很多业务面临的痛点问题,接下来介绍两个真实案例。

1、真实案例一:组件升级,导致线上服务查询超时。

  • 原因:老版本组件查询数据库时会自动类型转换(int -> string),新版本不会,导致查询没命中索引。如果下图所示,如果id字段在数据库是字符串类型,组件升级前会命中索引,组件升级后不会。

2、真实案例二:框架升级,引发多起工单问题。

  • 原因 :Laravel框架由5.5.44升级到5.5.48,Cache设置有效期参数由分钟改成了秒,官方无任何发布说明。(参考:Laravel 5.5.48 的巨坑

小结:以上案例常见测试手段都无法提前发现问题,故障复盘的时候没有很好的改进措施。

1.2、开源方案

常见开源的回归测试解决方案,有:tcpcopygoreplaydiffy等,适合性能测试(真实流量压测)、适合只读接口功能回归测试等。

常见开源方案存在以下痛点问题:

1、不支持写接口回放:写接口存在非幂等性,有脏数据风险。

  • 非幂等性:写接口每次回放结果可能不一样,比如:第一次回放提示写入成功,第二次回放可能提示主键冲突,每次回放差异巨大没法对比测试。
  • 有脏数据风险:回放的时候会真实写入数据库,可能会因为你的测试导致数据库存在很多脏数据。

2、依赖下游测试环境:依赖下游服务和下游数据源。

  • 依赖下游服务:下游服务必须稳定才能进行测试,随着微服务的拆分这可能会是一个挑战。
  • 依赖下游数据源:线上日志获取的请求在线下测试可能行不通。比如:同一个用户ID线上有数据,线下测试可能没有数据,因为依赖的数据源不一样。

以下列举几个支持写接口回放的开源解决方案,后续我们实现的时候会重要参考。

  • rdebug:滴滴开源的php解决方案
  • sharingan:滴滴开源的go解决方案
  • jvm-sandbox-repeater:阿里开源的java解决方案,MTSC 2019 年度开源贡献奖。

二、原理

2.1、基本原理

基本原理:录制服务线上流量,通过回放对比发现潜在问题。先流量录制,再流量回放。

1、流量录制:上图绿色背景部分,以线上网络流量采集为主。

  • 思路:不仅录制接口请求和返回(上图1和8),还录制接口所有子调用请求和返回(上图2到7),将这些流量(上图1到8)都存储起来。
  • 难点:如何采集线上服务网络流量?(技术点:流量拦截)、如何将网络流量串联起来?(技术点:链路追踪

2、流量回放:上图红色背景部分,以线下网络流量对比为主。

  • 思路:回放过程中,会保持所有输入一致(含子调用返回值),对比所有输出的差异(含子调用请求差异),噪音、时间等干扰因素会被排除。
  • 难点:如何重定向到Mock Server?(技术点:连接重定向)、Mock Server如何匹配流量返回?(技术点:流量匹配

3、原理应用:各语言实现方案有差异,本文主要介绍PHP和JAVA实现。

  • PHP语言:主要参考开源的解决方案,做了部分改进,如:平台化改造、并发回放支持、存储优化等。
  • JAVA语言:基于上述原理创新实现,首个基于网络层流量实现写接口回放的方案。

2.2、PHP开源方案

PHP开源方案实现用到了动态库加载技术,先做个简单的介绍。

2.2.1、动态库加载技术

  • 动态库加载技术:使用LD_PRELOAD优先加载动态链接库。LD_PRELOAD是一个Linux环境变量,允许程序运行前优先加载动态链接库(可以用来写游戏外挂)。
  • 动态库加载应用:如何知道应用程序在执行期间打开了那些文件?(方案之一:可以通过重写libc open函数动态加载来实现)

2.2.2、流量录制实现

1、流量拦截:获取上图1、2、3、4流量。

  • 方案:通过动态库加载技术,重写libc的read/write函数完成。PHP应用所有网络调用都会经过libc,重写网络流量读写函数可以获取网络流量。

2、链路追踪:串联上图1、2、3、4流量。

  • 方案:利用语言特性,通过线程ID串联。PHP请求通常在一个线程内串行完成,通过libc拦截流量很容易获取线程ID。(优势:无侵入)

2.2.3、流量回放实现

1、连接重定向:所有下游调用连接重定向到Mock Server ,由Mock Server匹配线上录制流量返回。

  • 方案:通过动态库加载技术,重写libc connect函数完成 。(修改连接地址到统一的Mock Server)

2、流量匹配:Mock Server收到下游调用请求,会尽可能的匹配线上录制的流量,并给出相应返回值。

  • 方案:将回放请求按照固定大小切分(默认16字节),分别和线上录制的请求匹配打分,取得分最高的流量。
  • 打分:包含最小单元数据得1分,不包含最小单元数据得0分,噪音等因素会干扰。

2.3、JAVA创新方案

2.3.1、流量录制实现

1、方案对比:和PHP开源方案对比

  • 共同点流量拦截方式一样,都可以在libc层获取流量。底层网络调用都通过libc来完成,通过libc层拦截,可以获取所有网络调用流量。
  • 差异点链路追踪方式不一样,JAVA涉及多线程。

2、方案实现:分场景实现有差异

  • BIO场景 :常见http、mysql等下游调用,和接口请求在同一个线程,无需特别处理可以串联流量。(方案:在libc层拦截和串联流量

  • NIO场景 :常见redis、dubbo等下游调用,和接口请求不在一个线程,需要通过字节码增强技术拦截上报流量。(原理:同skywalking

  • 录制示例 :以NIO场景的Redis letture组件为例,我们和skywalking基于同样的拦截点(如图2-3-1),同样是基于Command指令(如图2-3-2),只是获取的信息不同(Skywalking方案:获取指令,如:GET foo;我们方案:获取网络字节流,如:*2 $3 GET $3 foo)。

2.3.2、流量回放实现

1、方案对比:和PHP开源方案对比

a、共同点:绝大部分能力可以复用

  • PHP和JAVA语言流量回放都在统一平台进行,绝大部分能力可以复用。可复用核心能力:连接重定向流量匹配、时间回退、协议解析、噪音去除、流量染色等。

b、差异点:部分场景需要特殊处理。

2、方案实现:本地缓存场景示例

a、流量录制:扩展协议,不限网络流量录制

  • 流量拦截:通过字节码增强技术,拦截本地缓存方法调用,序列化后存储。(已支持GuavaCache 、CaffeineCache组件 )
  • 链路追踪:本地缓存一般都是同步调用,不需要特别处理,根据接口所在主线程串联即可。

b、流量回放:构造请求,统一流量匹配返回

  • 连接重定向:没有网络调用,需要构造TCP请求,和其他请求一样通过Mock Server获取录制流量。
  • 流量匹配: Mock Server按照统一的策略打分匹配返回即可,核心流程不变。

2.3.3、方案对比

和开源阿里方案对比:解决问题场景一样,比较知名的是:jvm-sandbox-repeater

1、优势

a、插件维护成本更低。

  • 我们方案:方案更偏底层,很多组件可以复用。如:同步调用的组件(如: mysql、http 等),统一都在socket包拦截。
  • 阿里方案:方案偏应用层,不同组件无法复用。组件都需要单独实现,可能还需要区分版本,很多团队尝试后放弃根因。

b、回放流程可以通用。

  • 我们方案:不同语言大部分能力都可以复用,能做到体验一致。
  • 阿里方案:仅支持JAVA语言。

2、不足

a、部分场景不支持:只支持无状态协议回放,有状态协议不支持,如:https、grpc等。

  • 方案:部分含不支持协议的接口可以设置过滤,常见协议都支持, 如:http、redis、mysql 、dubbo等

b、可读性相对较差:我们方案使用网络流量录制和回放,相对可读性差,不利于后续噪音去除和定位问题。

  • 方案:需要解析协议,可以直接复用开源PHP/GO解决方案。如:mysql协议,只展示其中sql语句即可。

2.4、小结

  • 整体思路:分语言录制,统一平台回放。先流量录制(线上为主,网络流量采集为主),再流量回放(线下为主,网络流量对比为主)。
  • 流量录制:不同语言实现方式有差别,需单独实现(PHP利用语言特性在libc层就可以实现,JAVA有异步场景需要特殊处理)。
  • 流量回放:不同语言实现方式大同小异,可以复用(核心能力都可以复用,包含:连接重定向、流量匹配、时间回退、协议解析、噪音去除等)。

三、结语

如果你对流量回放原理和应用感兴趣,欢迎点赞、收藏、评论(必回)、转发~

相关推荐
码农派大星。10 分钟前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man1 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*1 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu1 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s1 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子1 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王1 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_857589362 小时前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
一只爱打拳的程序猿2 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring