对GoReplay实现流量复制在测试中的应用

2024软件测试面试刷题,这个小程序(永久刷题),靠它快速找到工作了!(刷题APP的天花板)-CSDN博客文章浏览阅读2.2k次,点赞85次,收藏11次。你知不知道有这么一个软件测试面试的刷题小程序。里面包含了面试常问的软件测试基础题,web自动化测试、app自动化测试、接口测试、性能测试、自动化测试、安全测试及一些常问到的人力资源题目。最主要的是他还收集了像阿里、华为这样的大厂面试真题,还有互动交流板块……https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502

安装

1、安装go环境

-- 下载压缩包 https://studygolang.com/dl
wget https://dl.google.com/go/go1.21.4.linux-amd64.tar.gz
-- 解压
tar -C /usr/local -zxvf go1.21.4.linux-amd64.tar.gz
-- 添加环境变量
vim /etc/profile
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
-- 生效环境变量
source /etc/profile
-- 获取go版本,进行验证
go version

图片

2、安装goreplay

-- 下载压缩包
wget https://github.com/buger/goreplay/releases/download/1.3.3/gor_1.3.3_x64.tar.gz
-- 解压
tar -zxvf gor_1.3.3_x64.tar.gz
-- 将可执行命令,加到环境变量中
mv gor /usr/local/bin

3、监听端口进行流量复制

-- 服务器监听8125端口,--output-stdout 指定输出端为控制台
gor --input-raw :8125 --output-stdout

使用post方式访问 http://10.1.1.6:8125/logs/save?logContent=test1

图片

10.1.1.6 服务器控制台日志打印:

图片

流量复制命令行选项

可用输入

–input-raw 用于捕捉 HTTP 流量,指定 IP 地址或接口和应用程序端口。
–input-file  承受流量输入的文件(–output-file),用来离线流量重放。
–input-tcp 用于接受来自多个Gor 实例的流量,可以实现多个Gor的聚合。

可用输出

–output-http 转发收到的流量到给定的ip+端口。
–output-file 记录收到的流量到文件,用于保留流量、离线复制流量。
–output-tcp 将收到的流量转发给另一个 Gor 实例。
–output-stdout 将收到的流量输出到控制台。

请求过滤

在某些时候我们只期望把生产环境的特定流量重放,或者禁止一些流量重放,这时候我们可以使用 GoReplay 的过滤功能;GoReplay 提供以下选项来提供过滤功能:

--http-allow-header: 允许重放的 HTTP 头 (支持正则)

--http-disallow-header: 不允许的 HTTP 头 (支持正则)
--http-allow-method: 允许重放的 HTTP 方法
--http-allow-url: 允许重放的 URL (支持正则)
--http-disallow-url: 不允许的 HTTP URL (支持正则)

下文是一些流量复制的命令,通过灵活组合不同的命令行选项,实现各种流量复制任务:

//离线流量复制: 监听本机8125 端口,将流量记录到文件
gor --input-raw :8125 --output-file requests_origin.gor
//离线流量复制: 从本地文件解析流量,发送到测试服务器
gor --input-file requests_origin.gor --output-http http://172.16.14.60:8125
//在线流量复制:监听本机8125 端口,将流量发送到测试服务器
gor --input-raw :8125 --output-http http://172.16.14.60:8125
//在线流量复制,限制请求方法
gor --input-raw :8125 --output-http http://172.16.14.60:8125 --http-allow-method: GET
//在线流量复制,限制URL
gor --input-raw :8125 --output-http http://172.16.14.60:8125 --http-allow-url: /api

离线流量复制

离线的流量复制分为两步:流量录制和流量重放。

1、在线上服务器启动流量录制,监听服务端口,流量输出到文件。

gor --input-raw :8125 --output-file requests_origin.gor

在服务器上生成requests_origin.gor文件,记录了请求信息:请求方法、路径、参数、header等

2、在测试服务器上启动流量重放,流量入口选择为文件,输出到测试应用。​​​​​​​

gor  --input-file /home/gor/requests_origin.gor --output-http http://172.16.14.60:8125

在线流量复制

分别在本地、1.6服务器上启动efs 实例,端口为8125。在1.6 上启动gor,监听 8125端口,转发到本地进行流量复杂验证。

gor  --input-raw :8125 --output-http http://172.16.14.60:8125

图片

使用post方式访问 http://10.1.1.6:8125/logs/save?logContent=test1。本地收到请求

图片

测试:流量复制是否会影响原始请求

使用jmeter进行测试,进行如下七个阶段的测试,监控各个阶段请求的响应时间:

阶段1:无流量复制

阶段2:打开流量复制,镜像服务未启动正常

阶段3:启动镜像服务

阶段4:关闭镜像服务器

阶段5:启动镜像服务,使用sleep 模拟慢请求

阶段6:关闭镜像服务器

阶段7:关闭流量复制

下图分别是RT在300ms、100ms、10ms 的接口是整个操作过程中的请求RT变化,可以看到进行流量复制一定程度上会影响原始请求。

图片

图片

图片

其他特性支持

1、流量放大

在进行离线流量重放时,通过 requests_origin.gor|200% 这种方式指定文件名,GoReplay 会以两倍的速率进行请求重放​​​​​​​

gor --input-file "requests_origin.gor|200%" --output-http "http://127.0.0.1:9798"

2、流量收缩

使用 goreplay做流量复制时,如果线上流量较大测试环境扛不住,可以配置通过 绝对数量限制 和 百分比数量限制 两种方式限制复制的流量。

绝对数量限制: 使用 --output-http "ADDRESS|N" 形式的参数时,GoReplay 会保证镜像的流量请求每秒不会超过 “N” 个。

百分比限制限制: 使用 --output-http "ADDRESS|N%" 形式的参数时,GoReplay 会保证镜像的流量维持在总流量的 “N%”

3、记录响应信息

想要完整的记录原始响应和重放响应的信息,需要添加如下两个配置:-input-raw-track-response, -output-http-track-response。

-input-raw-track-response 选项配置后,会记录原始请求的响应。-output-http-track-response选项配置后会记录重放请求的响应。

如下命令:监听当前服务器的8125端口,将流量复制到 172.16.14.60:8125 端口,同时记录到requests_origin文件,包括原始响应和重放响应。​​​​​​​

gor  
--input-raw :8125 
--output-http  http://172.16.14.60:8125 
--output-file /home/gor/requests_origin.gor 
-input-raw-track-response 
-output-http-track-response

4、流量文件解析​​​​​​​

1 df891fbdac100e3cbdcd1d84 1701737200353284133 0
POST /logs/save?logContent=test1 HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Cache-Control: no-cache
Postman-Token: 0f312cea-6133-448e-a5f4-702281b4de7a
Host: 10.1.1.6:8125
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 0


🐵🙈🙉
2 df891fbdac100e3cbdcd1d84 1701737200417378904 727175
HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 05 Dec 2023 00:46:39 GMT
Keep-Alive: timeout=60
Connection: keep-alive

83
{"success":true,"entity":"操作成功","list":null,"existList":null,"map":null,"schoolManager":false,"serviceCode":null,"count":0}
0


🐵🙈🙉
3 df891fbdac100e3cbdcd1d84 1701737201362941251 16637663
HTTP/1.1 200 
Transfer-Encoding: chunked
Connection: keep-alive
Content-Type: application/json
Date: Tue, 05 Dec 2023 00:46:39 GMT
Keep-Alive: timeout=60

83
{"success":true,"entity":"操作成功","list":null,"existList":null,"map":null,"schoolManager":false,"serviceCode":null,"count":0}
0
  1. 文件以三个猴做分割,分为多个请求和响应。

  2. 第一行开头的1、2、3代表这快内容是请求、原始响应、重放响应。

  3. 第一行中间是请求ID,在所有请求中是唯一的,原始响应和重放响也会保持不变,用于请求和响应之间建立关联

  4. 第四个参数它表示延迟(消息开始和结束之间的持续时间)

  5. 然后就是正常的http 请求、响应的header 和body

5、使用middleware 实现自动化比较

middleware 模块

middleware是一个Gor的可扩展模块,通过 STDIN 接受请求和响应内容,通过 STDOUT发出修改后的请求。

                   Original request      +--------------+
+-------------+----------STDIN---------->+              |
|  Gor input  |                          |  Middleware  |
+-------------+----------STDIN---------->+              |
                   Original response (1) +------+---+---+
                                                |   ^
+-------------+    Modified request             v   |
| Gor output  +<---------STDOUT-----------------+   |
+-----+-------+                                     |
      |                                             |
      |            Replayed response                |
      +------------------STDIN----------------->----+

实现思路

1、利用gor 提供的middleWare模块,这个模块可以读取到原始请求、原始响应、重放响应。

2、原始请求、原始响应、重放响应可以使用requestId进行关联,从而实现比较。

实现细节

官方dome :

https://github.com/buger/goreplay/blob/master/examples/middleware/echo.java

命令行:gor --input-raw :8125 --output-http  http://172.16.14.60:8125 --output-file /home/gor/requests_origin.gor -input-raw-track-response -output-http-track-response --middleware "java -jar /home/gor/gor_diff-jar-with-dependencies.jar"

java 代码:​​​​​​​

package org.example;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;


/**
 * description
 *
 * @author king
 * 2023年11月25日
 */
@Slf4j
public class Echo {

    public static void main(String[] args) throws DecoderException {
        if (args != null) {
            for (String arg : args) {
                log.info(arg);
            }

        }
        Map<String, String> requestMap = new HashMap<>();
        Map<String, String> responseMap = new HashMap<>();

        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        String line = null;

        try {
            while ((line = stdin.readLine()) != null) {
                String decodedLine = decodeHexString(line);
                LogType type = getType(decodedLine);
                String requestId = getRequestId(decodedLine);
                log.info("type:{}, requestId: {}", type, requestId);
                if (type == LogType.ORIGINAL_REQUEST) {
                    requestMap.put(requestId, decodedLine);
                } else if (type == LogType.ORIGINAL_RESPONSE || type == LogType.REPLAYED_RESPONSE) {
                    if (responseMap.containsKey(requestId)) {
                        log.info("requestId: {} Response compare", requestId);
                        String decodedLine2 = responseMap.get(requestId);
                        String response = getResponse(decodedLine);
                        String response2 = getResponse(decodedLine2);
                        boolean res = compareBody(response, response2);
                        if (!res) {
                            log.error("请求:{} 的响应不一致\n 原始请求:{} \n 原始响应:{} \n 重放响应:{}",
                                    requestId, requestMap.get(requestId), type == LogType.ORIGINAL_RESPONSE ? decodedLine : decodedLine2,
                                    type == LogType.REPLAYED_RESPONSE ? decodedLine : decodedLine2);
                        } else {
                            log.info("请求:{} 的响应一致", requestId);
                        }
                        requestMap.remove(requestId);
                        responseMap.remove(requestId);
                    } else {
                        responseMap.put(requestId, decodedLine);
                        log.info("type:{}, requestId: {} add Response to Map", type, requestId);
                    }
                }
//                log.info("type:{}, requestId: {}, line: {}", type, requestId, line);
                System.out.println(line);
            }
        } catch (IOException e) {
            log.error("error", e);
        }
    }

    public static String decodeHexString(String s) throws DecoderException {
        return new String(Hex.decodeHex(s.toCharArray()));
    }

    public static LogType getType(String info) {
        int id = Integer.parseInt(info.substring(0, 1));
        return LogType.getById(id);
    }

    public static String getRequestId(String info) {
        int to = info.indexOf(' ', 2);
        return info.substring(2, to);
    }

    public static String getResponse(String info) {
        return info.substring(info.indexOf("\n") + 1);
    }

    public static boolean compareBody(String response1, String response2) {
        if (response1 == null && response2 == null) {
            return true;
        }
        if (response1 == null || response2 == null) {
            return false;
        }
        return StringUtils.equals(getBody(response1), getBody(response2));
    }

    public static String getBody(String response) {
        if (!response.contains("\r\n\r\n")) {
            return "";
        }
        int start = response.indexOf("\r\n", response.indexOf("\r\n\r\n") + 4) + 2;
        int end = response.indexOf("\r\n", start);
        return response.substring(start, end);
    }

}

执行效果:

行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 786229024,里面有各种测试开发资料和技术可以一起交流哦。

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】在这里插入图片描述
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值