有奖:语音产品征文挑战赛火热进行中> HOT
文档中心 > 腾讯云可观测平台 > 云压测 > 最佳实践 > 使用云压测回放 GoReplay 录制的请求

前言

本文将通过一个实例演示如何使用 GoReplay 录制 Nginx 网关接收到的请求,并将请求各个字段保存成 CSV 文件。在云压测中,通过上传 CSV 参数文件,指定期望的并发数,分布式回放请求到用户指定的地址。

GoRePlay 简介

GoReplay 是一个开源的流量录制回放工具。主要用于捕获实时流量并将其复制到测试环境中。
由于 GoReplay 本身并不提供一个分布式运行方案,只能在单机上运行。在流量录制完成后,受限于单机资源瓶颈,我们很难大规模的重放录制的流量,无法 有效的模拟真实用户流量的压测行为以及极限测试。而腾讯云云压测是一款分布式性能测试服务,可模拟海量用户的真实业务场景。因此我们可以引入云压测,使用云压测来回放 GoReplay 录制的真实流量。

常见 GoReplay 使用场景

性能测试:通过复制生产环境的流量到测试环境,可以在不影响真实用户的情况下对应用程序进行压力测试和性能评估。
故障排除和调试:当生产环境出现问题时,可以捕获相关的流量并在一个隔离的环境中重放,以便开发人员可以安全地调试问题而不会影响实际服务。
回归测试:在发布新版本之前,可以使用 GoReplay 捕获的流量来验证更改是否会引入新的错误或性能问题。
A/B 测试:可以将流量同时发送到两个服务版本,比较它们的表现,以便做出数据驱动的决策。
通过在回放时候,加大回放请求的倍数,模拟高流量情况,可以帮助确定在不同负载下所需的资源量。

GoReplay 流量录制原理

GoReplay 流量录制是监听指定端口流量,录制成 gor 文件(或者发送到其他目的端),方便后续回放。
sudo gor --input-raw :8080 --output-file requests.gor

开始录制回放用户 Nginx 网关

本文以录制回放 Nginx 网关为例,其他所有类型的网关都可以按照相同的方式来录制请求,然后使用云压测来回放用户请求。

环境准备

Nginx 网关:Nginx 网关上有源源不断的用户请求,需要在 Nginx 网关录制下这些请求。
GoReplay:请求录制回放工具。
安装 GoReplay 至网关所在机器上,如果网关所在机器是 Linux 或 macOS,可以使用以下命令:
# 从官方 GitHub 仓库下载最新的二进制文件
curl -L https://github.com/buger/goreplay/releases/download/1.3.3/gor_1.3.3_x64.tar.gz | tar xz

# 将二进制文件移动到你的PATH目录中,例如/usr/local/bin
mv gor /usr/local/bin/
说明:
确保替换上面的 URL 中的版本号为最新的版本,仓库地址:https://github.com/buger/goreplay
CSV 生成服务:接收 HTTP 请求,将接收到的请求各个字段写入 CSV 文件中。
云压测:基于用户上传的 CSV 文件,回放用户录制的所有请求。

实验流程

将 Nginx 上的请求录制成 Gor 文件

本节参与组件(其他组件仅做完整场景展示):Nginx 网关、GoReplay。
整体架构图如下:



开始录制流量前,需要在网关所在服务器上运行 GoReplay。以下是一个基本的命令示例,它会监听网关上的80端口,并将捕获的流量保存到一个文件中:
sudo gor --input-raw :80 --output-file requests.gor
这个命令会捕获所有通过端口80的流量,并将其保存到当前目录下的 requests.gor 文件中。
注意:
需要 sudo 权限来监听80端口。

将 Gor 文件转换成 CSV 参数文件

本节使用 GoReplay 回放 Gor 文件中记录的请求到 CSV 生成服务。参与组件:GoReplay、CSV 生成服务。
整体架构如下:


在 CSV 文件中会记录下请求各个字段,例如 scheme、host、uri、method、base64Body。下面是一个简单 CSV 文件示例:
scheme
host
uri
method
jsonHeaders
base64Body
http
mockhttpbin.pts.svc.cluster.local
/get?page=1
get
{"name":"kk"}
-
http
mockhttpbin.pts.svc.cluster.local
/post
post
{"Hello": 'world',}
dGhpcyBpcyBnb29kCg==
scheme,host,uri,method,jsonHeaders,base64Body
http,mockhttpbin.pts.svc.cluster.local,/get?page=1,get,{"name":"kk"},
http,mockhttpbin.pts.svc.cluster.local,/post,post,{"hello":"world"},dGhpcyBpcyBnb29kCg==
说明:
使用 base64Body,而不是直接记录 body 是因为有些请求的 body 发送的二进制文件,直接写入 CSV 文件会展示成乱码。写成 base64 后,方便后续转换成原来的结构体。
CSV 生成服务代码
编写服务代码,并保存为 main.go 文件。
package main

import (
"encoding/base64"
"encoding/csv"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)

func main() {
http.HandleFunc("/", requestHandler) // 设置处理函数
log.Println("Server starting on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}

func requestHandler(w http.ResponseWriter, r *http.Request) {
// 获取请求信息
scheme := "http" // 默认为http,因为Go的http包不支持直接获取scheme
if r.TLS != nil {
scheme = "https"
}
host := r.Host
uri := r.RequestURI
method := r.Method

// 将headers转换为JSON格式
headersJson, err := json.Marshal(r.Header)
if err != nil {
http.Error(w, "Error converting headers to JSON", http.StatusInternalServerError)
return
}

// 读取请求体
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
defer r.Body.Close()

// Base64编码请求体
base64Body := base64.StdEncoding.EncodeToString(body)

// 写入CSV文件
record := []string{scheme, host, uri, method, string(headersJson), base64Body}
err = writeToCSV(record)
if err != nil {
http.Error(w, "Error writing to CSV", http.StatusInternalServerError)
return
}

// 发送响应
fmt.Fprintf(w, "Request logged")
}

func writeToCSV(record []string) error {
file, err := os.OpenFile("requests.csv", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return err
}
defer file.Close()

writer := csv.NewWriter(file)
defer writer.Flush()

return writer.Write(record)
}
编译并运行 CSV 生成服务
1. 将上述文件保存成 main.go 文件
2. 直接运行代码。
go run main.go
回放流量到 CSV 生成服务上,用来生成 CSV 文件。
gor --input-file requests.gor --output-http "http://csv-server-address:8080" --http-original-host true
这个命令会读取 requests.gor 文件中的流量,并将其回放到CSV生成服务上,CSV 生成服务默认会将接收到的请求写成 requests.csv 文件里;且生成的流量 host 为请求原本的 host 而非 CSV 服务的地址。

在云压测上使用 CSV 参数文件回放请求

云压测支持用户上传 CSV 文件作为参数文件。您可以动态引用其中的测试数据,供脚本里的变量使用。这样,当施压机并发执行这段代码,每条请求能动态、逐行获取 CSV 里的每行数据,作为请求参数使用。参数文件具体用法可参见 使用参数文件
1. 登录 云压测控制台,云压测对于首次使用的用户提供一个免费的压测资源包。
2. 在左侧导航栏中选择测试场景,单击新建场景,选择脚本模式
云压测脚本模式支持原生 JavaScript ES2015(ES6)+ 语法,并提供额外函数,帮助您在脚本模式下,快速编排压测场景。您可在控制台的在线编辑器里,用 JavaScript 代码描述您的压测场景所需的请求编排、变量定义、结果断言、通用函数等逻辑。(详细的 API 文档请参见:PTS API)。
3. 上传之前录制的 CSV 文件,作为参数文件。



4. 编写压测脚本,施压机每次执行压测脚本时候,读取 CSV 文件中下一行,利用 CSV 文件中记录的字段重新构造出原始请求。



压测脚本如下:
// Send a http get request
import http from 'pts/http';
import { check, sleep } from 'pts';
import util from 'pts/util';
import dataset from 'pts/dataset';


export default function () {
// 读取csv文件各个字段
var method = dataset.get("method")
var scheme = dataset.get("scheme")
var host = dataset.get("host")
var uri = dataset.get("uri")
var jsonHeaders = dataset.get("jsonHeaders")
var base64Body = dataset.get("base64Body")

var headers = JSON.parse(jsonHeaders)
var body = util.base64Decoding(base64Body, "std", "b")

// 构造请求
var req = {
method: method,
url: scheme + "://" + host + uri,
headers: headers,
body: body
}

// 发送请求
var resp = http.do(req)

// simple get request
console.log(resp.body);
check('status is 200', () => resp.statusCode === 200, resp);
// sleep 1 second
sleep(1);
}
5. 点击保存并运行,即可运行压测脚本,回放流量。查看压测报告及请求采样,观察请求是否符合预期。



请求采样: