导图社区 流量录制回放
流量录制回放,为什么要做流量录制回放如何实现?为什么要用线上流量?常见的方案,业内案例。一篇思维导图,带你了解!
编辑于2022-07-08 12:23:25流量录制&回放
概念
为什么要做流量录制&回放?
测试回归覆盖率
测试用例不足或遗漏难以覆盖所有场景,导致回归测试费时费力,线上稳定性存在隐患,通过真实流量录制在回归测试时进行覆盖
全链路压测
有了线上的真实数据,全链路压测时覆盖度更广泛,数据更真实,省去了造数据的麻烦
故障演练
用线上流量覆盖度更广泛,数据更真实
如何实现?
录制线上的流量,然后回放到线上生产环境(需打标)或线下压测环境,可对线上流量进行倍速放大
为什么要用线上流量,而不是线下造流量?
使用线上流量覆盖度更广泛,数据更真实
难点
1. 如何能准确地获取线上流量数据;2. 如何能针对特定的业务流程产生的数据进行录制;3. 如何实现特定场景的数据回放;4. 如何保证对现有程序的无侵入性
流量回放时需全链路透传压测/演练标识,并需要做到全链路数据隔离
全链路透传标识
HTTP请求
HTTP header
需要针对 HTTPClient/OkHttp/RestTemplate 分别设置请求头
RPC请求
Dubbo attachment
MQ
消息头
包括 RabbitMQ/Kafka
异步请求
自定义线程或线程池
InheritableThreadLocal 可以实现父子线程上下文的传递,但对于线程复用的场景(线程池),子线程对应的父线程会不断变更,然而 InheritableThreadLocal 在子线程中不会同步进行更新操作。此时可以使用阿里开源的 transmittable-thread-local,这个框架可以解决线程复用场景下上下文传递的问题
全链路数据隔离
包括 MySQL、Redis、MongoDB、ES、HBase、日志和第三方系统的数据隔离等
影子表隔离
影子库是指跟生产库完全不同的库,影子表是指生产库里的一张表结构完全相同的表
为了不用在JavaAgent里初始化数据源和切换数据源,而SQL里一定有表名(其它存储服务类似),故我们使用影子表,而不使用影子库
可用Druid解析SQL拿到表名,然后在表名后加上“_shadow”后缀
Redis 总共有 256 个 DB,我们可以选择一个 DB 作为影子库,例如最后一个,影子流量的缓存操作都在 DB255 上面进行,对于 Key 也可以做改造,避免出现了问题无法排查的情况,可以在原 Key 的基础上添加_shadow”等后缀。因为 Redis 的相关框架没有提供扩展的功能,所以需要使用代码织入技术,将切点选择在 Redis 的调用处
Redis没有表的概念,那只能对key做影子隔离,一定要设置过期时间,而且过期时间应尽量短
影子目录隔离
压测/演练一般会发出大量的请求,相应的日志也会非常多,压测/演练流量的日志很容易覆盖正常流量的日志。所以我们改写了 logback 的 appender,若是压测/演练流量则将日志写入影子目录下,并且配置默认的删除策略,例如日志文件只保存三天
Mock隔离
对于第三方接口,例如支付,我们不能真的将压测/演练流量打过去,需要对这类请求进行Mock。具体做法为在这类方法上加上@Mock注解,并写上Mock内容,JavaAgent解析@Mock注解后直接返回Mock内容
是否需要对各种请求进行Mock,比如Http请求、Dubbo请求、MQ请求?我们做Mock只是针对需要调用外部系统的场景,而我们内部系统只会通过http接口方式调用外部接口,故不需要针对多种请求类型进行Mock
可直接加注解,也可配置在agent的配置文件里,agent读取配置后增强指定类的指定方法,并可配置参数类型(防止重载方法)
@Mock注解里可以设置延迟时间,sleep该延迟时间后再返回
Guava/Caffeine/Ehcache等内存缓存也需要写插件
JavaAgent
为了透传标识和数据隔离,我们需要使用JavaAgent技术改写框架层代码,需针对各种组件/中间件分别写插件
微内核+SPI插件
监控&链路跟踪
做压测和故障演练时,需通过监控系统多关注各服务的运行情况,出问题后可通过链路跟踪系统排查问题
数据偏移
字段偏移可以极大的保证业务数据的安全。偏移字段一般选择用户ID、商品ID等关联字段,如果有用到Sequence类的分布式ID组件,也需要进行偏移。根据业务的实际增长选择不同的偏移量,一般会选择10年以上都不会用到的值作为偏移量
常见方案
JVM-Sandbox-Repeater
原理
通过字节码增强的形式,对目标接口进行拦截,以获取接口参数和返回值,效果等价于AOP中的环绕通知
录制
1. 把repeater注入到service A中,用于监测service A,从而对service A进行录制2. 用户对service A发起请求3. sandbox感知到对service A发起的请求4. sandbox感知请求后通知repeater5. repeater对事件进行过滤和采样计算,对满足录制条件的请求会记录请求、响应、子调用和响应,序列化后通知repeater-console6. repeater-console把需要录制的请求、响应以及子调用序列化后的结果进行数据库存储
一条流量的调用链路包括入口调用和若干次子调用,流量的录制过程就是把入口调用和子调用通过一个唯一ID绑定成一次完整的调用记录。找到入口调用和子调用合适的代码点(关键入口和出口),基于字节码插桩技术在该代码点进行代码增强,实现调用拦截,记录调用的入参和返回值,然后根据相应的调用类型(如dubbo/http)生成一个录制标识。当调用完成时,就采集到了整个流量的调用记录,之后进行数据脱敏、序列化等操作,最后加密发送到服务端进行储存
回放
1. 用户请求repeater-console的回放接口,明确需要回放哪些录制数据。2. repeater-console调用repeater提供的回放任务接收接口,并进行下发回放任务。3. repeater在执行回放任务的过程中,会反序列化记录的wrapperRecord,根据信息构造相同的请求,对被挂载的任务进行请求,并跟踪回放请求的处理流程,以便记录回放结果以及执行mock动作4. 回放结束,repeater会将回放信息和结果序列化后通知repeater-console进行处理和数据保存
JavaRepeater 通过反射的方式调用对应的方法、传入参数触发回放;HttpRepeater 通过构造urlPath、port、header、请求参数、请求方法进行回放
流量回放是通过获取录制流量的入口调用,再次对迭代后的系统发起调用,然后去验证系统逻辑正确性的过程。和录制不一样的是,回放对于外部的调用都是Mock的,这个过程不会真正的去访问数据库。回放过程会将录制子调用和回放子调用的入参进行对比,如果参数不一致那么会阻断回放流量,如果参数一致会使用录制子调用的结果进行Mock返回。回放完成同样会产生一个响应结果,这个时候我们会对比原始录制结果和回放响应结果,根据该对比结果和子调用的对比结果就能得出被测试系统的正确性了
repeater & repeater-console
repeater是真正做事情的,具体来说就是真正负责录制和回放请求的,而 console 是负责管理所有的repeater的,如保存录制数据、下发回放任务
repeater 的启动模式有两种:attach 模式和 agent 模式
所以非常明显,repeater是通过JavaAgent技术实现的
二开
1)扩展了mongodb、es、hbase、kafka等插件(主要成本在于寻找最合适的插桩埋点)2)将录制、回放结果都存入es3)增加了批量获取录制和回放结果、批量回放等批量接口4)增加了接口黑名单(支持正则表达式)5)支持接口粒度的采样率(对于高频请求,采样率可以小一点)6)对最终的回放结果diffy做降噪处理(即有些字段不做比较)
插件开发流程
1.定义切面(需拦截的类和方法)2.定义增强逻辑(跟SkyWalking的插件开发流程一致)
主要特性
支持流量录制、回放
支持插件机制
已支持http/dubbo/mybatis/java/redis等插件
不是只要录制入口的 http/rpc 的调用就行了吗,至于 mybatis/java/redis 之类的不是会自动执行到吗,为什么还需要另外录制?那岂不是会执行到2次?
需要入侵运行服务的JVM
如何降噪?
何为噪声?
.时间差异
如果响应的Response有时间戳会造成差异(流量录制和回放时间戳不同)
随机数差异
请求参数或者返回结果中使用了随机数字
自增数据差异
无论请求还是返回,自增数据也会造成比对失败
链路ID标识
类似链路ID、SequenceID等标识,在系统中会透传,可能造成回放失败
返回无序元素集合
在方法接口调用返回时无序的元素集合,也会造成结果对比误差
如何降噪?
降噪配置
通过提供全局、应用级别、接口级别对噪音字段进行配置,配置可以解决一部分问题,适合存在通用性字段且数量不多的情况。可以作为降噪的第一层配置
智能降噪
通过对流量diff的自动对比的降噪方式(如Twitter开源框架Diffy)
通过三个版本系统进行流量对比,将其噪声过滤:候选版本:即待提测上线版本稳定版本:可以部署线上Master分支稳定版本副本:可以部署线上Master分支
1.通过向稳定版本和稳定版本副本回放流量,对比其流量差异得到【噪声】2.通过向候选版本和稳定版本回放流量,对比其流量差异得到【原始区别】3.再从【原始区别】剔除【噪声】得到最终的diff结果
GoReplay
依赖数据包捕获函数库(Packet Capture library)通过抓网络流量包,实现流量录制功能
tcpdump也依赖pcap库,可把GoReplay看成极简版的tcpdump
基于Go语言实现
主要特性
支持流量录制、回放、过滤,支持按倍速回放,回放时可改写接口参数
拓展方便:提供了插件机制,且插件实现不限制语言
资源消耗小,对业务无侵入
只支持HTTP,其它协议需二开
架构图
1. 用户设定录制参数,提交录制请求给压测服务2. 压测服务生成压测任务,并根据用户指定的参数生成录制命令3. Gor 控制器收到录制命令,回传“录制即将开始”的状态给压测服务,随后调起 GoReplay4. 录制结束,GoReplay 退出,Gor 控制器回传“录制结束”状态给压测服务5. Gor 控制器回传其他信息给压测系统6. 压测服务判定录制任务结束后,通知压测机将录制数据读取到本地文件中7. 录制任务结束
流量回放时,给回放流量打上压测标,以将回放流量要与真实流量区分开,如把 user-agent 改为 goreplay
gor-server作用
1. 可调起或终止 GoReplay 程序2. 屏蔽掉 GoReplay 使用细节,降低复杂度,提高易用性3. 回传状态,在 GoReplay 启动前、结束后、其他标志性事件结束后都会向压测系统回传状态4. 对录制和回放产生的数据进行处理与回传5. 打日志,记录 GoRepaly 输出的状态数据,便于后续排查
ngx_http_mirror_module
流量请求到nginx后,nginx正常转发请求到目标应用,同时复制流量到mirror服务后不再管控
特性
配置简单,nginx原生支持
支持配置多份镜像放大流量
实际业务中经由nginx转发的模块较多,无法筛选指定请求
只支持录制http流量
总结
架构图
1. 录制代理ReplayAgent负责接收控制台指令对GoReplay或sandbox-repeater管控2. 录制代理上报录制数据流量和监控信息3. 控制台对流量录制管理,如数据完整性、录制任务状态和结果、录制时间、录制流量过滤4. 控制台对流量回放管理,如:回放结果状态、时长设定、回放速度等5. 控制台与压测平台、回归测试平台的通信
业内案例
美团Quake
Quake集数据构造、压测隔离、场景管理、动态调控、过程监控、压测报告为一体,压测流量尽量模拟真实,具备分布式压测能力的全链路压测系统,通过模拟海量用户真实的业务操作场景,提前对业务进行高压力测试,全方位探测业务应用的性能瓶颈,确保平稳地应对业务峰值
Quake-Web:压测管理端,负责压测数据构造、压测环境准备、场景管理、压测过程的动态调整以及压测报表展示等。Quake-Brain:调度中心,负责施压资源的调度、任务分发与机器资源管理。Quake-Agent:压测引擎,负责模拟各种压测流量。Quake-Monitor:监控模块,统计压测结果,监控服务各项指标。
Quake-Web管理端
数据构造
在真实业务场景中,我们需要的是能直接回放业务高峰期产生的流量,只有面对这样的流量冲击,才能真实的反映系统可能会产生的问题。Quake 主要提供了 HTTP 和 RPC 的两种数据构造方式
HTTP 服务的访问日志收集
对于 HTTP 服务,在 Nginx 层都会产生请求的访问日志,我们对这些日志进行了统一接入,变成符合压测需要的流量数据
RPC 线上流量实时录制
对于 RPC 服务,服务调用量远超 HTTP 的量级,所以在线上环境不太可能去记录相应的日志。这里我们使用对线上服务进行实时流量录制,结合 RPC 框架提供的录制功能,对集群中的某几台机器开启录制,根据要录制的接口和方法名,将请求数据上报到录制流量的缓冲服务(Broker)中,再由Broker 生成最终的压测词表,上传到存储平台(S3)
压测隔离
做线上压测与线下压测最大不同在于,线上压测要保证压测行为安全且可控,不会影响用户的正常使用,并且不会对线上环境造成任何的数据污染。要做到这一点,首要解决的是压测流量的识别与透传问题。有了压测标识后,各服务与中间件就可以依据标识来进行压测服务分组与影子表方案的实施
测试标识透传
对于单服务来说,识别压测流量很容易,只要在请求头中加个特殊的压测标识即可,HTTP和RPC 服务是一样的。但要在整条完整的调用链路中始终保持压测标识则比较困难(可参考SkyWalking)
跨线程间的透传
以 Java 应用为例,主线程根据压测请求,将测试标识写入当前线程的 ThreadLocal 对象中,利用 InheritableThreadLocal 的特性,对于父线程 ThreadLocal 中的变量会传递给子线程,保证了压测标识的传递。而对于采用线程池的情况,同样对线程池进行了封装,在往线程池中添加线程任务时,额外保存了 ThreadLocal 中的变量,执行任务时再进行替换 ThreadLocal 中的变量
跨服务间的透传
对于跨服务的调用,我们对所有涉及到的中间件进行了一一改造。利用 Mtrace (公司内部统一的分布式会话跟踪系统)的服务间传递上下文特性,在原有传输上下文的基础上,添加了测试标识的属性,以保证传输中始终带着测试标识
压测服务隔离
在低峰期,机器基本都是处于比较空闲的状态。我们将根据业务的需求在线上对整条链路快速创建一个压测分组,隔出一批空闲的机器用于压测。将正常流量与测试流量在机器级别进行隔离,以降低压测对服务集群带来的影响
依赖标识透传的机制,Quake平台提供了基于IP、机器数、百分比不同方式的隔离策略,业务只需提供所需隔离的服务名,由 Quake 进行一键化的开启与关闭
压测数据隔离
针对写请求的压测,因为它会向真实的数据库中写入大量的脏数据。我们借鉴了阿里最早提出的“影子表”隔离的方案。“影子表”的核心思想是,使用线上同一个数据库,包括共享数据库中的内存资源,因为这样才能更接近真实场景,只是在写入数据时会写在了另一张“影子表”中
Quake-Brain调度中心
调度中心作为整个压测系统的大脑,它管理了所有的压测任务和压测引擎。基于自身的调度算法,调度中心将每个压测任务拆分成若干个可在单台压测引擎上执行的计划,并将计划以指令的方式下发给不同的引擎,从而执行压测任务
Quake-Monitor监控模块
压测肯定会对线上服务产生一定的影响,特别是一些探测系统极限的压测,我们需要具备秒级监控的能力,以及可靠的熔断降级机制
阿里的 Amazon/PTS京东的的 ForceBOT高德的 TestPG字节跳动的 Rhino