精华内容
下载资源
问答
  • 全链路压测、单接口压测、多接口混合压测和性能测试一些相关的概念
    2021-10-09 13:48:40
     根据压测的场景不同,或者压测的目的不同,我们会选择不一样的压测方式来进行压测,我梳理了下大概的压测的方式,主要有以下三个。
    
    1.全链路压测
    
    2.单接口压测
    
    3.多接口混合压测
    
            全链路压测呢,近几年 比较火,比如阿里啊,京东等公司都在做这个,全链路的压测呢,比较热门,基于用户的使用链路场景,或者系统的调用链路场景压测,更加贴近真实的用户场景。更多的去发现系统的问题。各个链路相关的接口的问题都能直接暴露出来。
    
        单接口压测呢,就是针对单一的接口进行压测,比如,我们有一个登录接口,一直登录超时,那么我们只需要对单独的登录接口进行压测。只压测一个登录接口,不牵涉到系统业务的其他的接口。
    
            多接口混合压测,对于多个接口,进行混合压测,比如,我们在有一个服务器,我想要知道  这个服务器上面部署的多个接口同时访问,服务器的压力是怎么样的,是否能够满足性能的要求。所以我们就采用了多接口混合压测。
    
     这以上列举的是常见的。大家在实际的工作当中,可以用到的,或者经常熟知的。我们针对不同的项目,不同的压测需求,来选择适合的压测方式。多种压测方式并行,才能达到最佳的效果,个人观点来看,先单接口,后多接口混合,最后全链路,循序渐进的过程才最有效。
    
     
    
    压测指标的来源
    
     我们压测的时候,肯定需要压测的性能指标的,合适的时间,选择合适的指标,那么我们来看看,我们场景的性能指标,有哪些来源方式
    
     
    
    •1. 来源:合作方要求
    
    •2.根据业务特征,梳理(产品,技术)
    
     1.合作方要求
    更多相关内容
  • 服务接口压测工具

    2018-10-22 09:55:13
    测试服务端接口并发能力,也可作为单元自测使用,可设置线程数
  • 接口压测总结

    2021-10-25 16:36:40
    首先明确压测的目的:为了在业务高峰期到来之前,确保服务器经得起高峰期的压力。 但是每个系统的用户数量以及业务量都不一样。那么,在压测之前就需要根据系统的规模,定个小目标。 一般情况,没啥人用的服务 tps...
    首先明确压测的目的:为了在业务高峰期到来之前,确保服务器经得起高峰期的压力。
    但是每个系统的用户数量以及业务量都不一样。那么,在压测之前就需要根据系统的规模, 定个小目标
    一般情况,没啥人用的服务 tps 20,RT300ms就行了
    十万到百万级的服务,响应能达到tps50 RT200ms就可以了
    后台服务,能达到tps 20 RT200ms即可(通常后台同时使用也没多少人)
    秒杀类的短时间高并发……TPS100或200 在 100ms内响应 应该也能撑一段时间(具体情况还是要看业务量)
    例如,我公司本次压测目标如下:
    1.TPS 100以上
    2.RT 600以内
    3.TPS和RT同时满足,且失败率不超过0.05%
    有了目标,接下来开始压测:
    1.准备压测工具(本次压测使用jmeter):
    前往jmeter官网下载jmeter,并解压。
    2.双击jmete.bat打开jmeter压测界面,设置压测参数。
    2.1  添加线程组:
    2.2添加HTTP header manager:

    本次为了能测到系统内部的接口,在header中设置已经登录的cookie信息
    2.3添加http请求:

    2.4(可选)添加Constant timer

     

    2.5添加View result tree

    3.启动压测
    4.生成压测报告

     

    这里需要设置三个路径:
    1.日志文件路径:和步骤2.5中的路径是同一个,压测时,日志会写入日志文件,然后这里生成压测报告需要按照日志文件中的内容来生成,所以要指定日志文件路径。
    2.配置文件路径:如果不需要特殊配置,默认选择jmeter安装bin目录下的jmeter.properties文件即可。
    3.报告生成目录:设置压测报告文件生成的目录。
    5.查看报告,一般情况,主要关注这几个地方:
    6.根据生成的报告编写压测报告
    展开全文
  • [TOC]Jmeter简介Jmeter是Apache开源的一个使用纯Java编写的压力测试工具,它最初是为测试web应用程序而设计的,但后来扩展到了其他测试功能。...如今Jmeter是一个主流的、功能完善且强大的压测工具,由于...

    [TOC]

    Jmeter简介

    Jmeter是Apache开源的一个使用纯Java编写的压力测试工具,它最初是为测试web应用程序而设计的,但后来扩展到了其他测试功能。例如,可用于测试静态和动态资源以及web动态应用程序的性能等。Jmeter可以用来模拟对服务器、服务器组、网络或对象上的重负载,以测试其强度或分析服务在不同负载类型下的总体性能。

    如今Jmeter是一个主流的、功能完善且强大的压测工具,由于是使用Java编写的,所以具有跨平台特性,可以运行在Windows、Mac、Linux等操作系统上。并且支持丰富的协议,如:HTTP/HTTPS、FTP、JDBC、SOCKET、SOAP。。。等,此外还有许多第三方插件支持以及第三方集成

    相关网址:

    配置Jmeter的测试计划

    通过以上给出的地址将Jmeter下载好后,进入到Jmeter的bin目录下,启动脚本放在这个目录,其中windows双击jmeter.bat即可,Mac和Linux下则是使用如下命令运行:

    sudo sh {JMETER_HOME}/bin/jmeter.sh

    初次运行可能会比较慢,启动成功后,界面如下:

    cbc7a6e41034d6eb9ed618343226f170.png

    接下来我们看看如何使用Jmeter对接口进行压测,我现在有一个商品列表接口,接口路径为:/goods/to_list。

    想要使用Jmeter对这个接口进行压测的话,首先右击左边栏的“Test Plan”(测试计划),添加一个“Thread Group”(线程组),如下图所示:

    3a8843ed27da6c467e2b36bab87761c1.png

    接着简单配置一下请求线程数之类的,线程数就代表着并发数:

    94347eeab7d1efcc3b3d2c0ea2bad713.png

    因为该接口是通过HTTP请求的,所以需要添加一个HTTP请求默认值,如下图:

    0bd8b84770715831ff340f4088821fa9.png

    配置接口服务的请求协议、ip地址以及端口号,这样后面添加HTTP请求的时候就不需要重复填写这些信息了,可以起到一个通用配置的作用:

    3a683a3dc3583971b8bac17bff5f2aab.png

    然后添加一个HTTP请求:

    44908c7c85ba314c3f9129cd94f0cdc2.png

    这里只需要配置接口路径以及请求方法即可,因为Jmeter会自动帮我们拼上之前配置的HTTP请求默认值:

    89409d4c0e538bbe5014287797651a3c.png

    为了查看测试结果,还需要添加一些监听器,监听器有很多种,也可以添加多个,这里我添加了一个聚合报告:

    7e082e03b6879b6b48695e22d6e029c9.png

    以及一个图形结果:

    5857d1813b9473eda04708eebd390c56.png

    开始测试

    在上一小节中,介绍了如何针对一个现有的接口配置测试计划,按照以上步骤配置完成后如下:

    992162f3df24e608e67463d6c164c969.png

    现在我们就可以开始执行这个测试计划了:

    d81d7f01cb6079892d88208ce4a4c8e5.png

    测试运行完成后,可以在聚合报告中看到压测的统计数据:

    86ab9a49e2b5237cff5c8b5152d904aa.png

    简单说明下这些统计指标:

    Samples:采样数/请求数

    Average:平均响应时间,单位毫秒

    Median:响应时间的一个中间值,单位毫秒

    90%Line:90%的请求响应时间,单位毫秒

    95%Line:95%的请求响应时间,单位毫秒

    99%Line:99%的请求响应时间,单位毫秒

    Min:请求最小响应时间,单位毫秒

    Maximum:请求最大响应时间,单位毫秒

    Error %:响应错误率

    Throughput:每秒的吞吐量

    Received KB/sec:每秒接收的数据量

    Send KB/sec:每秒发送的数据量

    如果此时查看图形结构的话,会发现只有一点点线条,这是因为我们将线程组的线程数设置太小了:

    ef17be819e81d70b8b3cb83259f2268d.png

    可以尝试将线程数设置到1000,然后清除之前的测试报告数据:

    4fd4985ff1020e51bcf0128fe95f5ec8.png

    然后再运行这个测试计划,运行完成后再打开图形结果就可以看到曲线图了,如下:

    63a530dd0d4325afdfaea7fa5c878a5b.png

    命令行压测

    以上小节介绍了Jmeter的基本使用,但都是基于可视化界面操作的。在某些情况下我们可能需要在服务器上进行测试,而绝大多数服务器系统都只有命令行。好在Jmeter支持命令行下的操作,所以本小节将介绍在命令行下如何使用Jmeter做压测。

    首先第一步是在当前的操作系统下在可视化界面上配置好测试计划并将其存储为jmx文件,在这一步我们直接将之前所演示的测试计划另存为jmx文件即可。“file” -> “Save Test Plan as”:

    ac1228b81cae1aff35271bf8b47427f7.png

    Tips:jmx文件内容格式为xml,实际上就是描述了测试计划的配置信息,感兴趣的话可以自行打开瞧一瞧

    第二步是将这个jmx文件上传到服务器上,我这里的操作系统是Mac OS,所以使用scp进行上传:

    scp MyWorkSpace/TestFiles/goods_list.jmx root@${server_ip}:/home/goods_list.jmx

    Tips:如果是windows系统,可以使用rz命令,直接选择上传的文件即可。

    上传完成后,使用Jmeter的shell脚本jmeter.sh执行该jmx文件:

    [root@server ~]# cd /usr/local/apache-jmeter-5.1.1/bin/

    [root@server /usr/local/apache-jmeter-5.1.1/bin]# ./jmeter.sh -n -t /home/goods_list.jmx -l /home/result.jtl

    参数说明:

    -n:非图形界面启动

    -t:指定需要执行的jmx文件路径

    -l:指定结果文件的存储路径

    执行该测试计划后,想要知道压测是否正常进行的话,可以使用top命令查看系统负载。我这里很明显是正常执行压测了,CPU负载达到了19.72:

    cc6be28c6b7233bdcb44f66ed687e73b.png

    执行结束后会输出一些测试结果的统计信息:

    27e1e788b82ce8770c603231251c935e.png

    正常情况下执行结束会生成一个测试结果文件,存放在我们使用参数-l指定的路径下。如下:

    e9f00e0767058fb6e48a43aa5479808d.png

    最后把生成的result.jtl文件下载到本地,并导入到Jmeter中。“Aggregate Report” -> “Browser” -> “Open”:

    64e6c6c3899b0dd3e4023433e49d6181.png

    此时就可以看到测试结果的聚合报告了:

    58bd71cfb23e2e59f6d0ff116a728b6a.png

    自定义变量模拟多用户

    上面两小节我们简单介绍了Jmeter的基本使用,而本小节则将介绍一个在Jmeter中很实用的功能:自定义变量。有过一些测试经验的小伙伴应该知道在很多接口的测试用例下,我们可能需要模拟多个用户对某一接口进行请求,这时候通过使用Jmeter里的自定义变量就能实现这个功能。当然自定义变量不仅仅是用来模拟多用户,这里只是举一个实际的应用例子。

    在一个线程组里可以添加多个HTTP请求,如果希望一次只测试一个接口的话,可以将其他测试请求给禁用掉。例如这里将之前所添加的“商品列表”给禁用,右击“商品列表”选择“Disable”:

    b9b8708f421fa5b8c5e02fe6b3698f1f.png

    现在我有一个可以通过token获取用户信息的接口:/user/info,该接口需要接收一个参数,即用户的登录token。根据该接口添加一个新的HTTP请求,具体配置如下:

    e97eef86b777a5110fd53795d8d3c912.png

    按照如此配置,每次请求传递的都是同一个token,这样就只能针对一个用户进行测试。若要模拟多用户,自然不能将token写死在这里,而是将其抽取成一个变量,这也就是所谓的自定义变量了。在Jmeter中自定义变量,首先需要添加一个“CSV Data Set Config”。如下图所示:

    c360a0623c489ece5d2ba779102f56a2.png

    然后创建一个配置文件,配置文件的内容是有一定格式的,需要使用分隔符分隔,分隔符可以是任意的可识别符号,例如我这里使用逗号进行分隔:

    $ cat config.txt

    13000000001,149787a6b7986f31b3dcc0e4e857cd2a

    13000000002,078563f337ec6d6fedf131ddc857db19

    13000000003,7692dcdc19e41e66c6ae2de54a696b25

    13000000004,0f3e84acb19dff22f695f31dbe3e972a

    13000000005,268e27056a3e52cf3755d193cbeb0594

    13000000006,00c66aaf5f2c3f49946f15c1ad2ea0d3

    接着根据配置文件和实际需求编辑如下选项,让Jmeter可以从配置文件中读取配置项:

    59d028983b2297255411db40ccf70c48.png

    Tips:这里定义的变量名中userId对应的是配置文件中的第一列,而token则是对应的第二列,以此类推。另外,分隔符必须与配置文件中的分隔符保持一致,这样Jmeter才能正常构建变量与配置的映射关系

    在使用的时候,通过${变量名}引用自定义变量即可,如下示例:

    a6ee1e838c41758138104b5200f8ae57.png

    这样Jmeter就会从配置文件中读取不同的token并赋值给我们定义好的变量,那么每次请求就可以传递不同的token,以达到模拟多个用户请求的效果。

    在上一小节中我们介绍过命令行下的压测方式,但如果我们配置的测试计划用到了自定义变量, 且定义了配置文件的路径。而服务器上的文件路径肯定和本地操作系统的文件路径不一样,那么要如何更改配置文件的路径呢?

    首先将jmx文件和配置文件一并上传到服务器上,接着打开jmx文件并查找到CSVDataSet标签,然后将描述配置文件路径的子标签的值改为服务器上配置文件所在的正确路径即可。如下图所示:

    a64c535d6e35a53fc1071df1966c935d.png

    Redis压测工具 - redis-benchmark

    Redis作为缓存中间件,其性能瓶颈会影响到系统的整体性能,所以通常我们也需要通过压测的方式确定Redis服务的性能瓶颈是多少。而Redis自带了压测工具:redis-benchmark,本小节将简单介绍该工具的使用。

    该工具的使用也比较简单,如果只是做一个压测的话,只需要使用如下命令即可:

    [root@server ~]# redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000

    参数说明:

    -h:指定host,即redis服务所在的ip地址

    -p:指定端口号

    -c:指定并发请求数量

    -n:指定发送多少个请求

    执行该命令后,控制台会输出相应的压测结果。观察输出的信息会发现,该命令会逐个对redis的一些主要指令进行压测,我这里就截取了get指令的压测结果。如下:

    ====== GET ======

    # 10w个请求在1.58秒内执行完毕

    100000 requests completed in 1.58 seconds

    # 100个并发客户端

    100 parallel clients

    # 每个请求数据包大小为3字节

    3 bytes payload

    keep alive: 1

    # 48.00%的请求耗时小于等于1毫秒

    48.00% <= 1 milliseconds

    # 99.66%的请求耗时小于等于2毫秒

    99.66% <= 2 milliseconds

    # 100%的请求耗时小于等于2毫秒

    100.00% <= 2 milliseconds

    # 每秒可完成63411.54个请求,即QPS

    63411.54 requests per second

    如果希望压测时指定每个请求的数据包大小可参考如下命令:

    [root@server ~]# redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100

    参数说明:

    -q:表示quiet,即不输出详细信息,仅输出每个指令的QPS信息

    -d:指定每个请求的数据包大小,单位为字节

    如果只想压测指定的命令,则可以使用-t参数来指定,如下示例:

    [root@server ~]# redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -q -n 100000

    如果想压测某条具体的指令,那么则可以使用script load来指定脚本。如下示例:

    [root@server ~]# redis-benchmark -h 127.0.0.1 -p 6379 -q -n 100000 script load "redis.call('set', 'foo', 'bar')"

    展开全文
  • └── server ├── client │ └── http_client.go http请求发起客户端 ├── dispose.go 执行入口 ├── golink │ └── http_link.go http接口具体的执行 └── statistics └── ...

    使用方式
    切换到根目录下,执行:

    go run main.go -c 500 -n 20 -u https://www.baidu.com
    

    在这里插入图片描述

    项目目录如下:
    在这里插入图片描述

    ├── go.mod    						 依赖管理
    ├── main.go    						 启动函数
    ├── model
    │   └── request.go					 对象定义
    └── server
        ├── client
        │   └── http_client.go			 http请求发起客户端
        ├── dispose.go					 执行入口
        ├── golink
        │   └── http_link.go			 http接口具体的执行
        └── statistics
            └── statistics.go			 统计测试结果
    

    main.go
    主要负责命令行传递的参数校验,然后启动server.Dispose()开始具体的执行

    import (
    	"flag"
    	"fmt"
    	"httpPressureTest/model"
    	"httpPressureTest/server"
    	"runtime"
    )
    
    type array []string
    
    func (a *array) String() string {
    	return fmt.Sprint(*a)
    }
    
    func (a *array) Set(s string) error {
    	*a = append(*a, s)
    
    	return nil
    }
    
    func main() {
    
    	runtime.GOMAXPROCS(1)
    
    	var (
    		concurrency uint64 // 并发数
    		totalNumber uint64 // 请求数(单个并发/协程)
    		requestUrl  string // 压测的url 目前支持,http/https ws/wss
    		headers     array  // 自定义头信息传递给服务器
    		body        string // HTTP POST方式传送数据
    	)
    
    	flag.Uint64Var(&concurrency, "c", 1, "并发数")
    	flag.Uint64Var(&totalNumber, "n", 1, "请求数(单个并发/协程)")
    	flag.StringVar(&requestUrl, "u", "", "压测地址")
    	flag.Var(&headers, "H", "自定义头信息传递给服务器 示例:-H 'Content-Type: application/json'")
    	flag.StringVar(&body, "data", "", "HTTP POST方式传送数据")
    
    	// 解析参数
    	flag.Parse()
    	if concurrency == 0 || totalNumber == 0 || (requestUrl == "") {
    		fmt.Printf("示例: go run main.go -c 1 -n 1 -u https://www.baidu.com/ \n")
    		fmt.Printf("当前请求参数: -c %d -n %d -u %s \n", concurrency, totalNumber, requestUrl)
    
    		flag.Usage()
    
    		return
    	}
    
    	request, err := model.NewRequest(requestUrl, headers, body)
    	if err != nil {
    		fmt.Printf("参数不合法 %v \n", err)
    
    		return
    	}
    
    	fmt.Printf("\n 开始启动  并发数:%d 请求数:%d 请求参数: \n", concurrency, totalNumber)
    	request.Print()
    
    	// 开始处理
    	server.Dispose(concurrency, totalNumber, request)
    
    	return
    }
    

    server.Dispose
    对统计模块statistics.ReceivingResults()和请求模块golink.Http()进行调度

    import (
    	"httpPressureTest/model"
    	"httpPressureTest/server/golink"
    	"httpPressureTest/server/statistics"
    	"sync"
    	"time"
    )
    
    
    // 处理函数
    func Dispose(concurrency, totalNumber uint64, request *model.Request) {
    
    	// 设置接收数据缓存
    	ch := make(chan *model.RequestResults, 1000)
    	var (
    		wg          sync.WaitGroup // 发送数据完成
    		wgReceiving sync.WaitGroup // 数据处理完成
    	)
    
    	wgReceiving.Add(1)
    	go statistics.ReceivingResults(concurrency, ch, &wgReceiving)
    
    	for i := uint64(0); i < concurrency; i++ {
    		wg.Add(1)
    		go golink.Http(i, ch, totalNumber, &wg, request)
    	}
    
    	// 等待所有的数据都发送完成
    	wg.Wait()
    
    	// 延时1毫秒 确保数据都处理完成了
    	time.Sleep(1 * time.Millisecond)
    	close(ch)
    
    	// 数据全部处理完成了
    	wgReceiving.Wait()
    
    	return
    }
    

    statistics.ReceivingResults
    接受并处理测试结果

    import (
    	"fmt"
    	"httpPressureTest/model"
    	"sort"
    	"strings"
    	"sync"
    	"time"
    )
    
    var (
    	// 输出统计数据的时间
    	exportStatisticsTime = 1 * time.Second
    )
    
    // 接收结果并处理
    // 统计的时间都是纳秒,显示的时间 都是毫秒
    // concurrent 并发数
    func ReceivingResults(concurrent uint64, ch <-chan *model.RequestResults, wg *sync.WaitGroup) {
    
    	defer func() {
    		wg.Done()
    	}()
    
    	var (
    		stopChan = make(chan bool)
    	)
    
    	// 时间
    	var (
    		processingTime uint64 // 处理总时间
    		requestTime    uint64 // 请求总时间
    		maxTime        uint64 // 最大时长
    		minTime        uint64 // 最小时长
    		successNum     uint64 // 成功处理数,code为0
    		failureNum     uint64 // 处理失败数,code不为0
    		chanIdLen      int    // 并发数
    		chanIds        = make(map[uint64]bool)
    		receivedBytes  int64
    	)
    
    	statTime := uint64(time.Now().UnixNano())
    
    	// 错误码/错误个数
    	var errCode = make(map[int]int)
    
    	// 定时输出一次计算结果
    	ticker := time.NewTicker(exportStatisticsTime)
    	go func() {
    		for {
    			select {
    			case <-ticker.C:
    				endTime := uint64(time.Now().UnixNano())
    				requestTime = endTime - statTime
    				go calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum, chanIdLen, errCode, receivedBytes)
    			case <-stopChan:
    				// 处理完成
    
    				return
    			}
    		}
    	}()
    
    	header()
    
    	for data := range ch {
    		processingTime = processingTime + data.Time
    
    		if maxTime <= data.Time {
    			maxTime = data.Time
    		}
    
    		if minTime == 0 {
    			minTime = data.Time
    		} else if minTime > data.Time {
    			minTime = data.Time
    		}
    
    		// 是否请求成功
    		if data.IsSucceed == true {
    			successNum = successNum + 1
    		} else {
    			failureNum = failureNum + 1
    		}
    
    		// 统计错误码
    		if value, ok := errCode[data.ErrCode]; ok {
    			errCode[data.ErrCode] = value + 1
    		} else {
    			errCode[data.ErrCode] = 1
    		}
    
    		receivedBytes += data.ReceivedBytes
    
    		if _, ok := chanIds[data.ChanId]; !ok {
    			chanIds[data.ChanId] = true
    			chanIdLen = len(chanIds)
    		}
    	}
    
    	// 数据全部接受完成,停止定时输出统计数据
    	stopChan <- true
    
    	endTime := uint64(time.Now().UnixNano())
    	requestTime = endTime - statTime
    
    	calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum, chanIdLen, errCode, receivedBytes)
    
    	fmt.Printf("\n\n")
    
    	fmt.Println("*************************  结果 stat  ****************************")
    	fmt.Println("处理协程数量:", concurrent)
    	fmt.Println("请求总数(并发数*请求数 -c * -n):", successNum+failureNum, "总请求时间:", fmt.Sprintf("%.3f", float64(requestTime)/1e9),
    		"秒", "successNum:", successNum, "failureNum:", failureNum)
    
    	fmt.Println("*************************  结果 end   ****************************")
    
    	fmt.Printf("\n\n")
    }
    
    // 计算数据
    func calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum uint64, chanIdLen int, errCode map[int]int, receivedBytes int64) {
    	if processingTime == 0 {
    		processingTime = 1
    	}
    
    	var (
    		qps              float64
    		averageTime      float64
    		maxTimeFloat     float64
    		minTimeFloat     float64
    		requestTimeFloat float64
    	)
    
    	// 平均 每个协程成功数*总协程数据/总耗时 (每秒)
    	if processingTime != 0 {
    		qps = float64(successNum*1e9*concurrent) / float64(processingTime)
    	}
    
    	// 平均时长 总耗时/总请求数/并发数 纳秒=>毫秒
    	if successNum != 0 && concurrent != 0 {
    		averageTime = float64(processingTime) / float64(successNum*1e6)
    	}
    
    	// 纳秒=>毫秒
    	maxTimeFloat = float64(maxTime) / 1e6
    	minTimeFloat = float64(minTime) / 1e6
    	requestTimeFloat = float64(requestTime) / 1e9
    
    	// 打印的时长都为毫秒
    	// result := fmt.Sprintf("请求总数:%8d|successNum:%8d|failureNum:%8d|qps:%9.3f|maxTime:%9.3f|minTime:%9.3f|平均时长:%9.3f|errCode:%v", successNum+failureNum, successNum, failureNum, qps, maxTimeFloat, minTimeFloat, averageTime, errCode)
    	// fmt.Println(result)
    	table(successNum, failureNum, errCode, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat, chanIdLen, receivedBytes)
    }
    
    
    // 打印表头信息
    func header() {
    	fmt.Printf("\n\n")
    	// 打印的时长都为毫秒 总请数
    	fmt.Println("─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────┬────────┬────────")
    	result := fmt.Sprintf(" 耗时│ 并发数│ 成功数│ 失败数│   qps  │最长耗时│最短耗时│平均耗时│下载字节│字节每秒│ 错误码")
    	fmt.Println(result)
    	// result = fmt.Sprintf("耗时(s)  │总请求数│成功数│失败数│QPS│最长耗时│最短耗时│平均耗时│错误码")
    	// fmt.Println(result)
    	fmt.Println("─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────┼────────┼────────")
    
    	return
    }
    
    // 打印表格
    func table(successNum, failureNum uint64, errCode map[int]int, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat float64, chanIdLen int, receivedBytes int64) {
    	var (
    		speed int64
    	)
    
    	if requestTimeFloat > 0 {
    		speed = int64(float64(receivedBytes) / requestTimeFloat)
    	} else {
    		speed = 0
    	}
    	// 打印的时长都为毫秒
    	result := fmt.Sprintf("%4.0fs│%7d│%7d│%7d│%8.2f│%8.2f│%8.2f│%8.2f│%8s|%8s│%v",
    		requestTimeFloat, chanIdLen, successNum, failureNum, qps, maxTimeFloat, minTimeFloat, averageTime,
    		fmt.Sprintf("%d", receivedBytes),
    		fmt.Sprintf("%d", speed),
    		printMap(errCode))
    	fmt.Println(result)
    
    	return
    }
    
    // 输出错误码、次数 节约字符(终端一行字符大小有限)
    func printMap(errCode map[int]int) (mapStr string) {
    
    	var (
    		mapArr []string
    	)
    	for key, value := range errCode {
    		mapArr = append(mapArr, fmt.Sprintf("%d:%d", key, value))
    	}
    
    	sort.Strings(mapArr)
    
    	mapStr = strings.Join(mapArr, ";")
    
    	return
    }
    

    golink.Http
    发起请求

    import (
    	"bytes"
    	"httpPressureTest/model"
    	"httpPressureTest/server/client"
    	"io/ioutil"
    	"net/http"
    	"sync"
    )
    
    type ReqListMany struct {
    	list []*model.Request
    }
    
    func (r *ReqListMany) getCount() int {
    	return len(r.list)
    }
    
    var (
    	clientList *ReqListMany
    )
    
    func init() {
    	clientList = &ReqListMany{}
    }
    
    func Http(chanId uint64, ch chan<- *model.RequestResults, totalNumber uint64, wg *sync.WaitGroup, request *model.Request) {
    
    	defer func() {
    		wg.Done()
    	}()
    
    	// fmt.Printf("启动协程 编号:%05d \n", chanId)
    	for i := uint64(0); i < totalNumber; i++ {
    
    		list := getRequestList(request)
    
    		isSucceed, errCode, requestTime, contentLength := sendList(list)
    
    		requestResults := &model.RequestResults{
    			Time:          requestTime,
    			IsSucceed:     isSucceed,
    			ErrCode:       errCode,
    			ReceivedBytes: contentLength,
    		}
    
    		requestResults.SetId(chanId, i)
    
    		ch <- requestResults
    	}
    
    	return
    }
    
    func getRequestList(request *model.Request) []*model.Request {
    
    	if len(clientList.list) <= 0 {
    
    		return []*model.Request{request}
    	}
    
    	return clientList.list
    }
    
    // 多个接口分步压测
    func sendList(requestList []*model.Request) (isSucceed bool, errCode int, requestTime uint64, contentLength int64) {
    
    	errCode = model.HttpOk
    	for _, request := range requestList {
    		succeed, code, u, length := send(request)
    		isSucceed = succeed
    		errCode = code
    		requestTime = requestTime + u
    		contentLength = contentLength + length
    		if succeed == false {
    
    			break
    		}
    	}
    
    	return
    }
    
    var buf = make([]byte, 1024*1024)
    
    // send 发送一次请求
    func send(request *model.Request) (bool, int, uint64, int64) {
    	var (
    		isSucceed     = false
    		errCode       = model.HttpOk
    		contentLength = int64(0)
    	)
    
    	newRequest := request
    
    	resp, requestTime, err := client.HttpRequest(newRequest.Method, newRequest.Url, newRequest.GetBody(), newRequest.Headers, newRequest.Timeout)
    	if err != nil {
    		errCode = model.RequestErr // 请求错误
    	} else {
    
    		contentLength = 0
    		for {
    			n, err := resp.Body.Read(buf)
    			resp.Body = ioutil.NopCloser(bytes.NewReader(buf))
    			contentLength += int64(n)
    			if err != nil {
    				break
    			}
    		}
    
    
    		// 验证请求是否成功
    		if resp.StatusCode == http.StatusOK {
    			isSucceed = true
    		} else {
    			errCode = resp.StatusCode
    		}
    
    	}
    	return isSucceed, errCode, requestTime, contentLength
    }
    

    client.HttpRequest
    request的请求体处理

    import (
    	"crypto/tls"
    	"fmt"
    	"io"
    	"net/http"
    	"time"
    )
    
    
    
    // HTTP 请求
    // method 方法 GET POST
    // url 请求的url
    // body 请求的body
    // headers 请求头信息
    // timeout 请求超时时间
    func HttpRequest(method, url string, body io.Reader, headers map[string]string, timeout time.Duration) (resp *http.Response, requestTime uint64, err error) {
    
    	// 跳过证书验证
    	tr := &http.Transport{
    		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    	}
    
    	client := &http.Client{
    		Transport: tr,
    		Timeout:   timeout,
    	}
    
    	req, err := http.NewRequest(method, url, body)
    	if err != nil {
    
    		return
    	}
    	// 在req中设置Host,解决在header中设置Host不生效问题
    	if _, ok := headers["Host"]; ok {
    		req.Host = headers["Host"]
    	}
    	// 设置默认为utf-8编码
    	if _, ok := headers["Content-Type"]; !ok {
    		if headers == nil {
    			headers = make(map[string]string)
    		}
    		headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
    	}
    
    	for key, value := range headers {
    		req.Header.Set(key, value)
    	}
    
    	startTime := time.Now()
    	resp, err = client.Do(req)
    	requestTime = uint64(time.Since(startTime))
    	if err != nil {
    		fmt.Println("请求失败:", err)
    		return
    	}
    
    	return
    }
    

    model.request
    存放实体类

    import (
    	"fmt"
    	"io"
    	"net/http"
    	"strings"
    	"time"
    )
    
    const (
    	HttpOk         = 200 // 请求成功
    	RequestTimeout = 506 // 请求超时
    	RequestErr     = 509 // 请求错误
    	ParseError     = 510 // 解析错误
    
    	FormTypeHttp      = "http"
    	TimeOut  = 30 * time.Second
    
    )
    
    // 验证方法
    type VerifyHttp func(request *Request, response *http.Response) (code int, isSucceed bool)
    
    
    // 请求结果
    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模式
    }
    
    func (r *Request) GetBody() (body io.Reader) {
    	body = strings.NewReader(r.Body)
    
    	return
    }
    
    func (r *Request) GetDebug() bool {
    
    	return r.Debug
    }
    
    
    // NewRequest
    // url 压测的url
    // verify 验证方法 在server/verify中 http 支持:statusCode、json webSocket支持:json
    // timeout 请求超时时间
    // debug 是否开启debug
    // path curl文件路径 http接口压测,自定义参数设置
    func NewRequest(url string, reqHeaders []string, reqBody string) (request *Request, err error) {
    
    	var (
    		method  = "GET"
    		headers = make(map[string]string)
    		body    string
    	)
    
    	// body取值
    	if reqBody != "" {
    		method = "POST"
    		body = reqBody
    		headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
    	}
    
    	// 获取请求头
    	for _, v := range reqHeaders {
    		getHeaderValue(v, headers)
    	}
    
    	// url拼接
    	form := FormTypeHttp
    	if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
    		url = fmt.Sprintf("http://%s", url)
    	}
    
    	request = &Request{
    		Url:     url,
    		Form:    form,
    		Method:  strings.ToUpper(method),
    		Headers: headers,
    		Body:    body,
    		Timeout: TimeOut,
    	}
    
    	return
    
    }
    
    func getHeaderValue(v string, headers map[string]string) {
    	index := strings.Index(v, ":")
    	if index < 0 {
    		return
    	}
    
    	vIndex := index + 1
    	if len(v) >= vIndex {
    		value := strings.TrimPrefix(v[vIndex:], " ")
    
    		if _, ok := headers[v[:index]]; ok {
    			headers[v[:index]] = fmt.Sprintf("%s; %s", headers[v[:index]], value)
    		} else {
    			headers[v[:index]] = value
    		}
    	}
    }
    
    // 打印
    func (r *Request) Print() {
    	if r == nil {
    
    		return
    	}
    
    	result := fmt.Sprintf("request:\n form:%s \n url:%s \n method:%s \n headers:%v \n", r.Form, r.Url, r.Method, r.Headers)
    	result = fmt.Sprintf("%s data:%v \n", result, r.Body)
    	result = fmt.Sprintf("%s \n timeout:%s \n debug:%v \n", result, r.Timeout, r.Debug)
    	fmt.Println(result)
    
    	return
    }
    
    
    // 请求结果
    type RequestResults struct {
    	Id            string // 消息Id
    	ChanId        uint64 // 消息Id
    	Time          uint64 // 请求时间 纳秒
    	IsSucceed     bool   // 是否请求成功
    	ErrCode       int    // 错误码
    	ReceivedBytes int64
    }
    
    func (r *RequestResults) SetId(chanId uint64, number uint64) {
    	id := fmt.Sprintf("%d_%d", chanId, number)
    
    	r.Id = id
    	r.ChanId = chanId
    }
    
    展开全文
  • Locust接口压测框架

    2022-04-21 12:02:46
    基于locust二次开发,通过yaml配置文件编写接口测试用例,一键执行压测并生成html报告。压测配置通过修改配置文件设置,支持分布式压测
  • Jmeter接口压测方法

    千次阅读 2020-12-31 23:16:55
    目录 一、Jmeter是什么 二、使用Jmeter 1、cmd中输入Jmeter(前提:已安装并配置好Jmeter) 2、更改语言为中文 3、创建基本设置(本次不设断言) ...Apache JMeter是Apache组织开发的基于Java的压力测试工具。...
  • 性能测试-接口压测

    2022-03-10 14:31:55
    性能测试-接口压测 1、理出压测接口清单(功能?对应的接口?) 2、在本地写好写脚本 3、基准测试(跑一个线程,运行成功) 4、准备数据(看测试的功能来确定) 5、压测,根据需求来判断 6、聚合报告里面有吞吐量、...
  • http接口压测工具wrk

    2021-08-04 23:02:35
    wrk是一款简单的HTTP压测工具,当运行在单个多核CPU上时,它能够产生巨大的负载。它结合了多线程设计和可伸缩的事件通知系统,如epoll和kqueue。 github:https://github.com/wg/wrk 国内镜像: ...
  • Linux接口压测

    2021-01-14 18:11:18
    第一步:将ServerAgent-2.2.1压缩包通过远程连接工具ssh上传到linux服务器上 第二步:进行解压该文件 第三步:解压...书写sql语句 第六步:将ip改为linux的端口号 第七步:填写完数据后进行压测 测压结束,结果如下:
  • 1.安装jmeter后,到配置...3.创建线程组后,将名字修改为线程组单接口(个人操作为了区分上面线程组),然后添加:取样器—>HTTP请求 4.在HTTP请求中,填入协议、IP地址和端口以及请求体数据等(如图所示) ...
  • 普通接口压测就是对接口的高频率访问 验证条件主要看两点,一是请求的成功率 二是请求的响应时间 辅助验证条件:可以看服务器的CPU以及内存的运行情况 实际操作 确定压测接口,设计好脚本,通知有关部门压测时间 ...
  • Linux服务器压测接口1. SpringBoot 接口打包,并用jar包方式部署二级目录三级目录 1. SpringBoot 接口打包,并用jar包方式部署 二级目录 三级目录
  • 接口性能压测工具——jmeter 并发测试、压力测试、接口测试
  • 一、压测前需知: 压测地址,路径,请求方法、请求头,请求参数,压测目标 二、本次压测任务比较简单,没有请求头,拿到参数后注意参数类型 1.先将图片进行base64位图片转换,拿到img值,device_id已知,...
  • 后台系统后台接口压测报告V1.0 文章目录后台系统后台接口压测报告V1.0测试目的测试内容测试环境测试方法压测工具和指标测试时间统计指标测试结果汇总表接口压测实况图测试结果分析 测试目的 针对uat环境的用户...
  • 这里简单的介绍下使用jmeter进行http接口压测方法 1、创建线程组 添加一线程组(即用户组:一个线程模拟一个用户行为,如果要模拟多个用户,则通过设置多线程来实现) 2、创建HTTP请求 因为是对http接口进行压测...
  • mac jmeter压测工具的安装
  • 前言前段时间有个项目即将上线,需要对其中的核心接口进行压测;由于我们的接口是 gRPC 协议,找了一圈发现压测工具并不像 HTTP 那么多。最终发现了 ghz 这个工具,功能也非常齐全。事...
  • 最近搞接口压测,学了下JMeter的基本使用,特此记录一下 一、下载 官网:https://jmeter.apache.org/download_jmeter.cgi 百度网盘:https://pan.baidu.com/s/1LJmLjhyf9Hi6wBSGhWmWvQ 提取码:ugt1 二、环境配置 ...
  • 性能测试—接口压测指标分析

    千次阅读 2020-11-26 14:25:53
    通常而言,Jmeter性能测试结果分析可从性能测试指标达成方面着手,然后再分析测试过程中出现的异常情况,逐一判断是否存在性能风险。 一、用户登录并发测试结果分析 1、提取测试指标 表1:用户登录并发性能指标 ...
  • 如何使用Jemeter对HTTP接口压测

    千次阅读 2021-11-30 22:09:20
    我们不应该仅仅局限于某一种工具,性能测试能使用的工具非常多,选择适合的就是最好的。笔者已经使用Loadrunner进行多年的...实际接口测试还可以使用Tsung、SoapUI等工具,但基于各方面考虑,最终采用了Jemeter。 Je
  • 接口压测: 1.压测注意接口是读还是写,是新增还是修改老接口,按不同标准压测 2.压测前,脚本和jmeter的压测配置设置好 3.压测时,避开高峰 4.压测后, 4.1、如果超时失败,记录失败的requestId,超时的,分析超时...
  • 接口压测流程-需求

    千次阅读 2019-04-18 14:23:26
    2、接口文档 (1)get/post (2)参数是否要做参数化,哪些要做 (3)请求和返回都要填(必填的参数) 3、压测场景 单点还是混合(混合的话比例是多少) (1)单点压基准,资源预警线:测出最优时间(理想用时...
  • Jmeter接口压测示例

    2020-05-12 03:52:03
    Jmeter接口压测示例 Apache JMeter™ 是 Apache 组织开发的一款开源软件,是典型的纯 Java 开发的应用程序,可以在不同平台比如Windows、Linux或macOS系统上进行软件测试。JMeter主要用于应用程序的功能负载测试以...
  • 双十一抢券接口压测

    千次阅读 2021-03-17 15:50:34
    一、用例编号:双十一抢券活动接口压测 二、压测需求:压测抢券接口能支持最高并发数 三、模块/功能: https://www-test.3weijia.com/activitys/api/seckill/sessions ...1)/api/s...
  • 一次简单的接口压测

    2019-03-21 08:42:14
    在拿到接口文档后 就可以进行接口测试了 首先打开JMETER右击测试计划添加线程组 然后在线程组上右击添加取样器-HTTP请求 最后再右击线程组在监听器中添加查看结果树 点击运行在查看结果树中查看运行结果...
  • 【接口测试】ab进行接口压测

    千次阅读 2019-09-05 15:29:43
    ab进行接口压测 1、安装 yum -y install httpd-tools 查看信息:ab -V 2、测试 上图是给出的ab工具压测某接口的情况。 下面我们对这些参数,进行相关说明。如下: -n 在测试会话中所执行的请求个数。默认...
  • 启动压测压测完成后可以分析聚合报告: #Samples代表在指定时间内请求次数(例如我这个查询接口是30s请求75171次) Average:平均响应时间——默认情况下是单个 Request 的平均响应时间,当使用了 Transaction ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,277
精华内容 10,110
关键字:

接口压测