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/20
1000=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)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/893867.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis --- 分布式锁的使用

我们在上篇博客高并发处理 --- 超卖问题一人一单解决方案讲述了两种锁解决业务的使用方法&#xff0c;但是这样不能让锁跨JVM也就是跨进程去使用&#xff0c;只能适用在单体项目中如下图&#xff1a; 为了解决这种场景&#xff0c;我们就需要用一个锁监视器对全部集群进行监视…

小盒科技携手体验家,优化智能教育服务体验,打造在线教育新高度

北京小盒科技有限公司&#xff08;简称“小盒科技”&#xff0c;由“作业盒子”更名而来&#xff09;是一家专注于教育科技的公司&#xff0c;致力于利用人工智能、大数据等先进技术&#xff0c;为中小学教育提供创新的解决方案和产品。 近日&#xff0c;「小盒科技」携手体…

PhotoShop中JSX编辑器安装

1.使用ExtendScript Tookit CC编辑 1.安装 打开CEP Resource链接&#xff1a; CEP-Resources/ExtendScript-Toolkit at master Adobe-CEP/CEP-Resources (github.com) 将文件clone到本地或者下载到本地 点击AdobeExtendScriptToolKit_4_Ls22.exe安装&#xff0c;根据弹出的…

ESP32-CAM实验集(WebServer)

WebServer 效果图 已连接 web端 platformio.ini ; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library stor…

强化学习数学原理(三)——值迭代

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

03:Heap代码的分析

Heap代码的分析 1、内存对齐2、Heap_1.c文件代码分析3、Heap_2.c文件代码分析4、Heap_4.c文件代码分析5、Heap_5.c文件代码分析 1、内存对齐 内存对齐的作用是为了CPU更快的读取数据。对齐存储与不对齐存储的情况如下&#xff1a; 计算机读取内存中的数据时是一组一组的读取的…

three.js+WebGL踩坑经验合集(5.2):THREE.Mesh和THREE.Line2在镜像处理上的区别

本文紧接上篇&#xff1a; (5.1):THREE.Line2又一坑&#xff1a;镜像后不见了 本文将解答上篇提到的3个问题&#xff0c;首先回答第二个问题&#xff0c;如何获取全局的缩放值。 scaleWorld这个玩意儿呢&#xff0c;three.js官方就没提供了。应该说&#xff0c;一般的渲染引…

jQuery的系统性总结

前言 jQuery是一个快速、小型且功能丰富的 JavaScript 库&#xff08;实际上就是一堆JS代码&#xff09;。其目的在于&#xff1a;write less do more。 优点&#xff1a; 写得少做得多&#xff1b;兼容性&#xff1b;体积小&#xff1b;链式编程&#xff1b;隐式迭代、插件丰…

【背包问题】完全背包

目录 前言&#xff1a; 一&#xff0c;完全背包问题 问题描述&#xff1a; 模板题目&#xff1a; 题目解析&#xff1a; 代码&#xff1a; 空间优化&#xff1a; 二&#xff0c;典例 1&#xff0c;零钱兑换 题目解析&#xff1a; 算法分析&#xff1a; 代码&#xff…

【Python实现机器遗忘算法】复现2023年TNNLS期刊算法UNSIR

【Python实现机器遗忘算法】复现2023年TNNLS期刊算法UNSIR 1 算法原理 Tarun A K, Chundawat V S, Mandal M, et al. Fast yet effective machine unlearning[J]. IEEE Transactions on Neural Networks and Learning Systems, 2023. 本文提出了一种名为 UNSIR&#xff08;Un…

知识管理平台在企业信息化建设中的应用价值与未来展望

内容概要 在当今信息化时代&#xff0c;企业面临着海量信息的挑战&#xff0c;知识管理平台因此应运而生&#xff0c;成为企业提升管理效率和决策能力的关键工具。知识管理平台不仅仅是一个信息存储的工具&#xff0c;它集成了信息共享、协作与创新、决策支持等多项功能。通过…

MiniHack:为强化学习研究提供丰富而复杂的环境

人工智能咨询培训老师叶梓 转载标明出处 想要掌握如何将大模型的力量发挥到极致吗&#xff1f;叶老师带您深入了解 Llama Factory —— 一款革命性的大模型微调工具&#xff08;限时免费&#xff09;。 1小时实战课程&#xff0c;您将学习到如何轻松上手并有效利用 Llama Facto…

从AD的原理图自动提取引脚网络的小工具

这里跟大家分享一个我自己写的小软件&#xff0c;实现从AD的原理图里自动找出网络名称和引脚的对应。存成文本方便后续做表格或是使用简单行列编辑生成引脚约束文件&#xff08;如.XDC .UCF .TCL等&#xff09;。 我们在FPGA设计中需要引脚锁定文件&#xff0c;就是指示TOP层…

MySQL分表自动化创建的实现方案(存储过程、事件调度器)

《MySQL 新年度自动分表创建项目方案》 一、项目目的 在数据库应用场景中&#xff0c;随着数据量的不断增长&#xff0c;单表存储数据可能会面临性能瓶颈&#xff0c;例如查询、插入、更新等操作的效率会逐渐降低。分表是一种有效的优化策略&#xff0c;它将数据分散存储在多…

HTML5使用favicon.ico图标

目录 1. 使用favicon.ico图标 1. 使用favicon.ico图标 favicon.ico一般用于作为网站标志&#xff0c;它显示在浏览器的地址栏或者标签上 制作favicon图标 选择一个png转ico的在线网站&#xff0c;这里以https://www.bitbug.net/为例。上传图片&#xff0c;目标尺寸选择48x48&a…

【C++动态规划 网格】2328. 网格图中递增路径的数目|2001

本文涉及知识点 C动态规划 LeetCode2328. 网格图中递增路径的数目 给你一个 m x n 的整数网格图 grid &#xff0c;你可以从一个格子移动到 4 个方向相邻的任意一个格子。 请你返回在网格图中从 任意 格子出发&#xff0c;达到 任意 格子&#xff0c;且路径中的数字是 严格递…

fatal error C1083: ޷[特殊字符]ļ: openssl/opensslv.h: No such file or directory

一、环境 1. Visual Studio 2017 2. edk2&#xff1a;202305 3. Python&#xff1a;3.11.4 二、 fatal error C1083: ޷&#xbfab0;ļ: openssl/opensslv.h: No such file or directory 上图出现这个警告&#xff0c;不用管。 出现Done&#xff0c;说明编译成功。 执行上…

组件框架漏洞

一.基础概念 1.组件 定义&#xff1a;组件是软件开发中具有特定功能或特性的可重用部件或模块&#xff0c;能独立使用或集成到更大系统。 类型 前端 UI 组件&#xff1a;像按钮、下拉菜单、导航栏等&#xff0c;负责构建用户界面&#xff0c;提升用户交互体验。例如在电商 AP…

隐藏字符造成的linux命令执行失败(非常难绷)

隐藏字符问题发生情景 事情是这样的&#xff0c;为了方便主机和虚拟机之间数据的传输&#xff0c;我打算建一个共享文件夹。由于我选择的是手动挂载&#xff0c;在VirtualBox 中创建好共享文件夹后&#xff0c;我着手打开Ubuntu&#xff0c;想将这个共享文件夹挂载到我的家目录…

C/C++ 虚函数

虚函数的定义 虚函数是指在基类内部声明的成员函数前面添加关键字 virtual 指明的函数虚函数存在的意义是为了实现多态&#xff0c;让派生类能够重写(override)其基类的成员函数派生类重写基类的虚函数时&#xff0c;可以添加 virtual 关键字&#xff0c;但不是必须这么做虚函…