基于Go语言实现一个压测工具

2025-01-28 16:50
文章标签 语言 工具 实现 go 压测

本文主要是介绍基于Go语言实现一个压测工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下...

本篇主要是基于Go来实现一个压测的工具,关于压测的内容可以参考其他的文章,这里默认了解压测的基本概念

基于golang实现的压测工具

整体架构

基于Go语言实现一个压测工具

整体系统架构比较简单

通用数据处理模块

Http请求响应数据处理

本项目支持http协议、websocket协议、grpc协议、Remote Authentication Dial-In User Service协议,因此需要构造出一个通用的http请求和响应的结构体,进行一个通用的封装:

// Request 请求数据
type Request struct {
	URL       string            // URL
	Form      string            // http/webSocket/tcp
	Method    string            // 方法 GET/POST/PUT
	Headers   map[string]string // Headers
	Body      string            // body
	Verify    string            // 验证的方法
	Timeout   time.Duration     // 请求超时时间
	Debug     bool              // 是否开启Debug模式
	MaxCon    int               // 每个连接的请求数
	HTTP2     bool              // 是否使用http2.0
	Keepalive bool              // 是否开启长连接
	Code      int               // 验证的状态码
	Redirect  bool              // 是否重定向
}

这当中值得注意的是验证的方法,这里是因为在进行压测中,要判断返回的响应是否是正确的响应,因此要进行判断响应是否正确,所以要进行相应的函数的注册,因此对于一个请求,是有必要找到一个对应的请求方法来判断这个请求正确,之后进行记录

这个model的核心功能,就是生成一个http请求的结构体,来帮助进行存储

// NewRequest 生成请求结构体
// url 压测的url
//China编程 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 *CURL
		curl, 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 string
	form, url = getForm(url)
	if form == "" {
		err = fmt.Errorf("url:%s 不合法,必须是完整http、webSocket连接", url)
		return
	}
	var ok bool
	switch form {
	case FormTypeHTTP:
		// verify
		if verify == "" {
			verify = "statusCode"
		}
		key := fmt.Sprintf("%s.%s", form, verify)
		_, ok = verifyMapHTTP[key]
		if !ok {
			err = errors.New("验证器不存在:" + key)
			return
		}
	case FormTypeWebSocket:
		// verify
		if 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 // 消息ID
	ChanID        uint64 // 消息ID
	Time          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.www.chinasem.cnRequest) 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.ClientConn
    address string
}

conn和Address主要都是借助于两个类的成员函数来完成,解析地址和建立连接

其余模块可在代码中查看,这里不进行过多讲述

https://gitee.com/zhaobohan/stress-testing/blob/master/server/client/grpc_client.go

Websocket客户端处理

// WebSocket webSocket
type WebSocket struct {
    conn       *websocket.Conn
    URLLink    string
    URL        *url.URL
    IsSsl      bool
    HTTPHeader 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 = false
        errCode   = 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 := sendLiphpst(chanID, list)
		requestResults := &model.RequestResults{
			Time:          requestTime,
			IsSucceed:     isSucceed,
			ErrCode:       errCode,
			ReceivedBytes: contentLength,
		}
		requestRejavascriptsults.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              float64
		averageTime      float64
		maxTimeFloat     float64
		minTimeFloat     float64
		requestTimeFloat 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) / 1e6
	mandroidinTimeFloat = float64(minTime) / 1e6
	requestTimeFloat = float64(requestTime) / 1e9
	// 打印的时长都为毫秒
	table(successNum, failureNum, errCode, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat, chanIDLen,
		receivedBytes)
}

以上就是基于Go语言实现一个压测工具的详细内容,更多关于Go压测工具的资料请关注编程China编程(www.chinasem.cn)其它相关文章!

这篇关于基于Go语言实现一个压测工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153257

相关文章

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文