Go:基于Go实现一个压测工具
文章目录
- 写在前面
- 整体架构
- 通用数据处理模块
- Http请求响应数据处理
- Curl参数解析处理
- 客户端模块
- Http客户端处理
- Grpc客户端处理
- Websocket客户端处理
- 连接处理模块
- Grpc
- Http
- 统计数据模块
- 统计原理
- 实现过程
写在前面
本篇主要是基于Go来实现一个压测的工具,关于压测的内容可以参考其他的文章,这里默认了解压测的基本概念
基于Golang实现的压测工具
整体架构
整体系统架构比较简单
通用数据处理模块
Http请求响应数据处理
本项目支持http协议、websocket协议、grpc协议、Remote Authentication Dial-In User Service协议,因此需要构造出一个通用的http请求和响应的结构体,进行一个通用的封装:
// Request 请求数据
type Request struct {URL string // URLForm string // http/webSocket/tcpMethod string // 方法 GET/POST/PUTHeaders map[string]string // HeadersBody string // bodyVerify string // 验证的方法Timeout time.Duration // 请求超时时间Debug bool // 是否开启Debug模式MaxCon int // 每个连接的请求数HTTP2 bool // 是否使用http2.0Keepalive bool // 是否开启长连接Code int // 验证的状态码Redirect bool // 是否重定向
}
这当中值得注意的是验证的方法,这里是因为在进行压测中,要判断返回的响应是否是正确的响应,因此要进行判断响应是否正确,所以要进行相应的函数的注册,因此对于一个请求,是有必要找到一个对应的请求方法来判断这个请求正确,之后进行记录
这个model的核心功能,就是生成一个http请求的结构体,来帮助进行存储
// NewRequest 生成请求结构体
// url 压测的url
// verify 验证方法 在server/verify中 http 支持:statusCode、json webSocket支持:json
// timeout 请求超时时间
// debug 是否开启debug
// path curl文件路径 http接口压测,自定义参数设置
func NewRequest(url string, verify string, code int, timeout time.Duration, debug bool, path string,reqHeaders []string, reqBody string, maxCon int, http2, keepalive, redirect bool) (request *Request, err error) {var (method = "GET"headers = make(map[string]string)body string)if path != "" {var curl *CURLcurl, err = ParseTheFile(path)if err != nil {return nil, err}if url == "" {url = curl.GetURL()}method = curl.GetMethod()headers = curl.GetHeaders()body = curl.GetBody()} else {if reqBody != "" {method = "POST"body = reqBody}for _, v := range reqHeaders {getHeaderValue(v, headers)}if _, ok := headers["Content-Type"]; !ok {headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"}}var form stringform, url = getForm(url)if form == "" {err = fmt.Errorf("url:%s 不合法,必须是完整http、webSocket连接", url)return}var ok boolswitch form {case FormTypeHTTP:// verifyif verify == "" {verify = "statusCode"}key := fmt.Sprintf("%s.%s", form, verify)_, ok = verifyMapHTTP[key]if !ok {err = errors.New("验证器不存在:" + key)return}case FormTypeWebSocket:// verifyif verify == "" {verify = "json"}key := fmt.Sprintf("%s.%s", form, verify)_, ok = verifyMapWebSocket[key]if !ok {err = errors.New("验证器不存在:" + key)return}}if timeout == 0 {timeout = 30 * time.Second}request = &Request{URL: url,Form: form,Method: strings.ToUpper(method),Headers: headers,Body: body,Verify: verify,Timeout: timeout,Debug: debug,MaxCon: maxCon,HTTP2: http2,Keepalive: keepalive,Code: code,Redirect: redirect,}return
}
之后是对于对应的响应的封装,结构体定义为:
// RequestResults 请求结果
type RequestResults struct {ID string // 消息IDChanID uint64 // 消息IDTime uint64 // 请求时间 纳秒IsSucceed bool // 是否请求成功ErrCode int // 错误码ReceivedBytes int64
}
Curl参数解析处理
对于这个模块,本项目中实现的逻辑是根据一个指定的Curl的文件,对于文件中的Curl进行解析,即可解析出对应的Http请求的参数,具体代码链接如下
https://gitee.com/zhaobohan/stress-testing/blob/master/model/curl_model.go
客户端模块
Http客户端处理
在该模块中主要是对于Http客户端进行处理,对于普通请求和Http2.0请求进行了特化处理,支持根据客户端ID来获取到指定的客户端,建立映射关系
具体的核心成员为:
var (mutex sync.RWMutex// clients 客户端// key 客户端id - value 客户端clients = make(map[uint64]*http.Client)
)
再具体的,对于客户端的封装,主要操作是,对于Client的构造
// createLangHTTPClient 初始化长连接客户端参数
// 创建了一个配置了长连接的 HTTP 客户端传输对象
func createLangHTTPClient(request *model.Request) *http.Client {tr := &http.Transport{// 使用 net.Dialer 来建立 TCP 连接// Timeout 设置为 30 秒,表示如果连接在 30 秒内没有建立成功,则超时// KeepAlive 设置为 30 秒,表示连接建立后,如果 30 秒内没有数据传输,则发送一个 keep-alive 探测包以保持连接DialContext: (&net.Dialer{Timeout: 30 * time.Second,KeepAlive: 30 * time.Second,}).DialContext,MaxIdleConns: 0, // 最大连接数,默认0无穷大MaxIdleConnsPerHost: request.MaxCon, // 对每个host的最大连接数量(MaxIdleConnsPerHost<=MaxIdleConns)IdleConnTimeout: 90 * time.Second, // 多长时间未使用自动关闭连接// InsecureSkipVerify 设置为 true,表示不验证服务器的 SSL 证书TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}if request.HTTP2 {// 使用真实证书 验证证书 模拟真实请求tr = &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second,KeepAlive: 30 * time.Second,}).DialContext,MaxIdleConns: 0, // 最大连接数,默认0无穷大MaxIdleConnsPerHost: request.MaxCon, // 对每个host的最大连接数量(MaxIdleConnsPerHost<=MaxIdleConns)IdleConnTimeout: 90 * time.Second, // 多长时间未使用自动关闭连接// 配置 TLS 客户端设置,InsecureSkipVerify 设置为 false,表示验证服务器的 SSL 证书TLSClientConfig: &tls.Config{InsecureSkipVerify: false},}// 将 tr 配置为支持 HTTP/2 协议_ = http2.ConfigureTransport(tr)}client := &http.Client{Transport: tr,}// 禁止 HTTP 客户端自动重定向,而是让客户端在遇到重定向时停止并返回最后一个响应if !request.Redirect {client.CheckRedirect = func(req *http.Request, via []*http.Request) error {return http.ErrUseLastResponse}}return client
}
https://gitee.com/zhaobohan/stress-testing/blob/master/server/client/http_client.go
Grpc客户端处理
对于Grpc的构造来说,主要实现的功能是建立连接等,这些操作是较为简单的操作,因此这里不具体讲述
// GrpcSocket grpc
type GrpcSocket struct {conn *grpc.ClientConnaddress string
}
conn和Address主要都是借助于两个类的成员函数来完成,解析地址和建立连接
其余模块可在代码中查看,这里不进行过多讲述
https://gitee.com/zhaobohan/stress-testing/blob/master/server/client/grpc_client.go
Websocket客户端处理
// WebSocket webSocket
type WebSocket struct {conn *websocket.ConnURLLink stringURL *url.URLIsSsl boolHTTPHeader map[string]string
}
其余模块可在代码中查看,这里不进行过多讲述
https://gitee.com/zhaobohan/stress-testing/blob/master/server/client/websocket_client.go
连接处理模块
Grpc
对于Grpc的测试,这里模拟了一个rpc调用,执行了一个Hello World的函数,之后填充相应的数据作为请求的响应,最后将结果返回
// grpcRequest 请求
func grpcRequest(chanID uint64, ch chan<- *model.RequestResults, i uint64, request *model.Request,ws *client.GrpcSocket) {var (startTime = time.Now()isSucceed = falseerrCode = model.HTTPOk)// 获取连接conn := ws.GetConn()if conn == nil {errCode = model.RequestErr} else {c := pb.NewApiServerClient(conn)var (ctx = context.Background()req = &pb.Request{UserName: request.Body,})// 发送请求,获得响应rsp, err := c.HelloWorld(ctx, req)if err != nil {errCode = model.RequestErr} else {// 200 为成功if rsp.Code != 200 {errCode = model.RequestErr} else {isSucceed = true}}}requestTime := uint64(helper.DiffNano(startTime))requestResults := &model.RequestResults{Time: requestTime,IsSucceed: isSucceed,ErrCode: errCode,}requestResults.SetID(chanID, i)ch <- requestResults
}
Http
对于Http的测试,效果也基本类似,原理也基本相同
// HTTP 请求
func HTTP(ctx context.Context, chanID uint64, ch chan<- *model.RequestResults, totalNumber uint64, wg *sync.WaitGroup,request *model.Request) {defer func() {wg.Done()}()for i := uint64(0); i < totalNumber; i++ {if ctx.Err() != nil {break}list := getRequestList(request)isSucceed, errCode, requestTime, contentLength := sendList(chanID, list)requestResults := &model.RequestResults{Time: requestTime,IsSucceed: isSucceed,ErrCode: errCode,ReceivedBytes: contentLength,}requestResults.SetID(chanID, i)ch <- requestResults}return
}
统计数据模块
下面来看计算统计数据模块
统计原理
这里需要统计的数据有以下:
耗时、并发数、成功数、失败数、qps、最长耗时、最短耗时、平均耗时、下载字节、字节每秒、状态码
其中这里需要注意的,计算的数据有QPS,其他基本都可以经过简单的计算得出
那QPS该如何进行计算呢?这里来这样进行计算:
QPS = 服务器每秒钟处理请求数量 (req/sec 请求数/秒)
定义:单个协程耗时T, 所有协程压测总时间 sumT,协程数 n
如果:只有一个协程,假设接口耗时为 2毫秒,每个协程请求了10次接口,每个协程耗总耗时210=20毫秒,sumT=20
QPS = 10/201000=500
如果:只有十个协程,假设接口耗时为 2毫秒,每个协程请求了10次接口,每个协程耗总耗时210=20毫秒,sumT=2010=200
QPS = 100/(200/10)*1000=5000
上诉两个示例现实中总耗时都是20毫秒,示例二 请求了100次接口,QPS应该为 示例一 的10倍,所以示例二的实际总QPS为5000
除以协程数的意义是,sumT是所有协程耗时总和
实现过程
这个模块主要是定时进行一个统计压测的结论并进行打印的工作,依赖的函数是
// calculateData 计算数据
func calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum uint64,chanIDLen int, errCode *sync.Map, receivedBytes int64) {if processingTime == 0 {processingTime = 1}var (qps float64averageTime float64maxTimeFloat float64minTimeFloat float64requestTimeFloat float64)// 平均 QPS 成功数*总协程数/总耗时 (每秒)if processingTime != 0 {qps = float64(successNum*concurrent) * (1e9 / float64(processingTime))}// 平均时长 总耗时/总请求数/并发数 纳秒=>毫秒if successNum != 0 && concurrent != 0 {averageTime = float64(processingTime) / float64(successNum*1e6)}// 纳秒=>毫秒maxTimeFloat = float64(maxTime) / 1e6minTimeFloat = float64(minTime) / 1e6requestTimeFloat = float64(requestTime) / 1e9// 打印的时长都为毫秒table(successNum, failureNum, errCode, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat, chanIDLen,receivedBytes)
}
相关文章:

Go:基于Go实现一个压测工具
文章目录 写在前面整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理 客户端模块Http客户端处理Grpc客户端处理Websocket客户端处理 连接处理模块GrpcHttp 统计数据模块统计原理实现过程 写在前面 本篇主要是基于Go来实现一个压测的工具,关于压测的内…...

算法-加油站问题
hello 大家好!今天开写一个新章节,每一天一道算法题。让我们一起来学习算法思维吧! function canCompleteCircuit(gas, cost) {// 加油站的总数const n gas.length;// 记录总剩余油量,若总剩余油量小于 0,说明无法绕环…...

UART ,IIC 和SPI三种总线协议
1.UART 1.1 简介 UART(Universal Asynchronous Receiver/Transmitter)即通用异步收发器。 常见的串行、异步通信总线,两条数据线Tx、Rx,实现全双工通信,常用于主机与外设的通信,点对点。 1.2 硬件连接 交叉…...
Padas进行MongoDB数据库CRUD
在数据处理的领域,MongoDB作为一款NoSQL数据库,以其灵活的文档存储结构和高扩展性广泛应用于大规模数据处理场景。Pandas作为Python的核心数据处理库,能够高效处理结构化数据。在MongoDB中,数据以JSON格式存储,这与Pandas的DataFrame结构可以很方便地互相转换。通过这篇教…...
动手学图神经网络(6):利用图神经网络进行点云分类
利用图神经网络进行点云分类 引言 在本教程中,大家将学习使用图神经网络(Graph Neural Networks, GNN)进行点云分类的基本工具。给定一组对象或点集的数据集,将这些对象嵌入到一个特征空间中,使得它们在特定任务下能够分类。将原始点云作为神经网络的输入,让网络学习捕…...

C语言从入门到进阶
视频:https://www.bilibili.com/video/BV1Vm4y1r7jY?spm_id_from333.788.player.switch&vd_sourcec988f28ad9af37435316731758625407&p23 //枚举常量 enum Sex{MALE,FEMALE,SECRET };printf("%d\n", MALE);//0 printf("%d\n", FEMALE…...
Python中容器类型的数据(下)
集合 集合 (set) 是一种可迭代的、无序的、不能包含重复元素的容器类型的数据。 Python中的集合是一种重要的数据结构,以下为你详细介绍: 定义与特点 无序性:集合中的元素没有固定顺序, {1, 2, 3} 和 {3, 2, 1} 在Python中是同一…...
MySQL 用户相关的操作详解
MySQL 5.x 用户操作 创建用户 在 MySQL 5.x 中,使用 GRANT 语句创建用户并授权: 语法 GRANT ALL PRIVILEGES ON *.* TO usernamehost IDENTIFIED BY password;username:用户名 host:指定用户可访问的主机,例如 loca…...

如何删除hugging face dowloaded的llm model?
如何删除hugging face dowloaded的llm model? 在现在需要使用llm进行research的情况下,经常会出现,由于下载模型太多,导致内存问题,然后需要删除某些不用的模型的情况,那么如何找到hugging face的模型保存…...
Vue 封装http 请求
封装message 提示 Message.js import { ElMessage } from "element-plus";const showMessage (msg,callback,type)>{ElMessage({message: msg,type: type,duration: 3000,onClose:()>{if (callback) {callback();}}}); }const message {error: (msg,…...

恒源云云GPU服务器训练模型指南
1数据上传 为了更方便的上传数据与下载数据,本例程采用xftp来完成数据的传输与下载。 XFTP下载链接,选择学生免费试用即可 2服务器的选择以及开启: 控制台->我的实例->点击创建实例 一般选择按量付费 接下来根据自己代码的torch版本…...
Spring Boot应用中实现基于JWT的登录拦截器,以保证未登录用户无法访问指定的页面
目录 一、配置拦截器进行登录校验 1. 在config层设置拦截器 2. 实现LoginInterceptor拦截器 3. 创建JWT工具类 4. 在登录时创建JWT并存入Cookie 二、配置JWT依赖和环境 1. 添加JWT依赖 2. 配置JWT环境 本篇博客将为大家介绍了如何在Spring Boot应用中实现基于JWT的登录…...
MySQL 基础学习(1):数据类型与操作数据库和数据表
MySQL 基础学习:数据类型与操作数据库和数据表 在这篇博客中,我们将深入学习 MySQL 的基础操作,重点关注数据库和数据表的操作,以及 MySQL 中常见的数据类型。希望本文能帮助你更好地理解和掌握 MySQL 的基本用法。 一、操作数据…...

zyNo.19
哈希(md5)绕过问题 本质上是弱类型问题的延申 题型 登录的哈希验证 $a ! $b Md5($a) md5($b) 解决办法Md5绕过 var_dump ("0e123456" "0e4456789"); //true 0e545993274517709034328855841020//true 参考资料0e开头的哈希…...
Kafka生产者ACK参数与同步复制
目录 生产者的ACK参数 ack等于0 ack等于1(默认) ack等于-1或all Kafka的同步复制 使用误区 生产者的ACK参数 Kafka的ack机制可以保证生产者发送的消息被broker接收成功。 Kafka producer有三种ack机制 ,分别是 0,1…...

IPhone14 Pro 设备详情
目录 产品宣传图内部图——后设备详细信息 产品宣传图 内部图——后 设备详细信息 信息收集于HubWeb.cn...

【Linux】磁盘
没有被打开的文件 文件在磁盘中的存储 认识磁盘 磁盘的存储构成 磁盘的效率 与磁头运动频率有关。 磁盘的逻辑结构 把一面展开成线性。 通过扇区的下标编号可以推算出在磁盘的位置。 磁盘的寄存器 控制寄存器:负责告诉磁盘是读还是写。 数据寄存器:给…...

Shell编程(for循环+并发问题+while循环+流程控制语句+函数传参+函数变量+函数返回值+反向破解MD5)
本篇文章继续给大家介绍Shell编程,包括for循环、并发问题,while循环,流程控制语句,函数传参、函数变量、函数返回值,反向破解MD5等内容。 1.for循环 for 变量 in [取值列表] 取值列表可以是数字 字符串 变量 序列…...

强化学习数学原理(三)——值迭代
一、值迭代过程 上面是贝尔曼最优公式,之前我们说过,f(v)v,贝尔曼公式是满足contraction mapping theorem的,能够求解除它最优的策略和最优的state value,我们需要通过一个最优v*,这个v*来计算状态pi*&…...

Day27-【13003】短文,什么是栈?栈为何用在递归调用中?顺序栈和链式栈是什么?
文章目录 第三章栈和队列总览第一节栈概览栈的定义及其基本操作如何定义栈和栈的操作?合理的出栈序列个数如何计算?栈的两种存储方式及其实现?顺序栈及其实现,还有对应时间复杂度*、清空栈,初始化栈5、栈空,…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...