流量回放上的调研

    探索如何利用流量回放,将线上真实的数据流转化为覆盖全面的回归测试用例。在调研了业内比较知名的流量回放方案,如 TcpReplay,TcpCopy 后。发现这些方案均只是服务端入口流量的 Copy 组件,虽然的确能将线上流量「复制 & 引流」至目标服务,如果仅仅是复制 HTTP 入口的流量,那么接收流量回放的服也要配套和被流量制服务业务数据一致的存,数据,第三方服

    能录制/回放应用调用链路入口(通常为 HTTP)的 Request/Response

    能录制/回放应用调用链路内部对的 DB,Redis 及其他服务的 Request/Response。

    能串联整个调用链路期间所有相关的录制/回放(一般都是考虑基于 Trace)

    能无限回放至任意环境(包括线上,线下,指定主机等)应用代码无侵入及录制过程对服务极低的性能耗损  。

    除了入口 HTTP Request/Response 处于链路的首位和末尾外。调用链路在应用内部的顺序是不确定的。比如可能先调用 DB 后调用 Redis,也可能先调用 Redis 后调用同样的 DB SQL 多次,是一个没有任何规律(也无法推测出规律)的调用顺序。

   所有录制的 Endpoint 都会同时被录制一个基于当前上下文的调用编号。由于回放时的调用链路与录制时的调用链路是一致的,所以可以通过编号准确的找到当前步骤需要回放的 Endpoint 及其数据。JVM-SANDBOX 来进行 AOP 方式的录制,看起来的确是切实可行的。

jvm-sandbox-repeater

jvm-sandbox-repeater框架基于JVM-Sandbox,具备了JVM-Sandbox的所有特点封装了以下能力:

1.录制/回放基础协议,可快速配置/编码实现一类中间件的录制/回放

2.开放数据上报,对于录制结果可上报到自己的服务端,进行监控、回归、问题排查等上层平台搭建

基于它,我们可以在业务系统无感知的情况下,快速扩展 api ,实现自己的插件,对流量进行录制,入口请求(HTTP/Dubbo/Java)流量回放、子调用(Java/Dubbo)返回值Mock能力。详细介绍可以看官方说明。

录制回放主要原理如下:

repeater作为sandbox一个子模块加载,进行流量录制发送console,console存储流量并且,调用repeater将流量在目标jvm上回放。

在这里插入图片描述

录制:如图,当repeater启动对service A的录制后,有请求到service A,sandbox感知到请求后通知repeater。repeater对事件进行给过滤和采样计算,对满足录制条件的请求会记录请求、响应、子调用和响应,序列化成后通知repeater-console进行处理和保存。

回放:回放时,用户请求repeater-console的回放接口,明确需要回放哪条录制数据。然后repeater-console通过调用repeater提供的回放任务接收接口下发回放任务。repeater在执行回放任务的过程中,会反序列化记录的wrapperRecord,根据信息构造相同的请求,对被挂载的任务进行请求,并跟踪回放请求的处理流程,以便记录回放结果以及执行mock动作。如图,当我们启用了redis插件,录制时,service A到reids等的子请求方法、参数、响应将被录制下来,回放时,当service A再对reids发起请求时,repeater会先判断是否需要mock,当需要mock时会根据回放上下文中的信息拼接出MockRequest,通过mock策略计算获取MockResponse。目前源码中是获取相似度100%的请求的响应来进行mock。回放结束,repeater会将回放信息和结果序列化后通知repeater-console进行处理和保存。

通过对 DB(MyBatis),Redis(RedisTemlate)等进行 AOP 拦截;

在相关 Endpoint 进行网络交互前记录(序列化)请求,并在网络交互后记录响应;

在请求/响应/及上文提到的调用编号均完备的情况下,使用 Json 序列为包含元数据(比如 Class 信息,数组或者集合的元素类型等)的字符串后推送至消息中间件(如 Kafka);repeater 是通过http发送同步请求(不过有内存队列)。随后通过 Repeat Service 异步消费后存入 DB。

流量录制回放代码入口

流量录制回放配置

@RequestMapping("/facade/api")
public class ConfigFacadeApi {

    @RequestMapping("/config/{appName}/{env}")
    public RepeaterResult<RepeaterConfig> getConfig(@PathVariable("appName") String appName,
                                                    @PathVariable("env") String env) {

        // 改为了可以适用于 gs-rest-service(自己的应用) 的配置
        RepeaterConfig config = new RepeaterConfig();
        List<Behavior> behaviors = Lists.newArrayList();
        config.setPluginIdentities(Lists.newArrayList("http", "java-entrance", "java-subInvoke", "mybatis", "ibatis"));
        // 回放器
        config.setRepeatIdentities(Lists.newArrayList("java", "http"));
        // 白名单列表
        config.setHttpEntrancePatterns(Lists.newArrayList("^/greeting.*$"));
        // java入口方法
        behaviors.add(new Behavior("hello.GreetingController", "greeting"));
        config.setJavaEntranceBehaviors(behaviors);
        List<Behavior> subBehaviors = Lists.newArrayList();
        // java调用插件
        config.setJavaSubInvokeBehaviors(subBehaviors);
        config.setUseTtl(true);
        return RepeaterResult.builder().success(true).message("operate success").data(config).build();
    }

}

 

代码repeater-module负责流量的录制工作,我们从repeater-module模块中,RepeaterModule.class来进行分析。
RepeaterModule实现了ModuleLifecycle接口,在sandbox进行模块加载时,对于实现该接口的实例会一次执行其onLoad方法、onActive方法、onCompleted方法,一起来看下onCompleted方法:

  1. 在线程池中执行,配置拉取,pullConfig实际上就是调用console提供的接口能力
  2. 根据配置进行初始化
  3. 启动心跳检测
public void loadCompleted() {
        ExecutorInner.execute(new Runnable() {
            @Override
            public void run() {
                configManager = StandaloneSwitch.instance().getConfigManager();
                broadcaster = StandaloneSwitch.instance().getBroadcaster();
                invocationListener = new DefaultInvocationListener(broadcaster);
                RepeaterResult<RepeaterConfig> pr = configManager.pullConfig();
                if (pr.isSuccess()) {
                    log.info("pull repeater config success,config={}", pr.getData());
                    ClassloaderBridge.init(loadedClassDataSource);
                    initialize(pr.getData());
                }
            }
        });
        heartbeatHandler = new HeartbeatHandler(configInfo, moduleManager);
        heartbeatHandler.start();
    }

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/weixin_36996888/article/details/109099352
今日推荐