本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目:
- 单台机器对 HTTP 短连接 QPS 1W+ 的压测实战
- 单台机器 100W 长连接的压测实战
目录
1、项目说明
1.1 go-stress-testing
go 实现的压测工具,每个用户用一个协程的方式模拟,最大限度的利用 CPU 资源
1.2 项目体验
可以在 mac/linux/windows 不同平台下执行的命令
go-stress-testing 压测工具下载地址
参数说明:
-c
-n*
-u
# 运行 以mac为示例
./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/
- 压测结果展示
执行以后,终端每秒钟都会输出一次结果,压测完成以后输出执行的压测结果
压测结果展示:
参数解释:
耗时: 程序运行耗时。程序每秒钟输出一次压测结果
并发数: 并发数,启动的协程数
成功数: 压测中,请求成功的数量
失败数: 压测中,请求失败的数量
qps: 当前压测的QPS(每秒钟处理请求数量)
最长耗时: 压测中,单个请求最长的响应时长
最短耗时: 压测中,单个请求最短的响应时长
平均耗时: 压测中,单个请求平均的响应时长
错误码: 压测中,接口返回的 code码:返回次数的集合
2、压测
2.1 压测是什么
压测,即压力测试,是确立系统稳定性的一种测试方法,通常在系统正常运作范围之外进行,以考察其功能极限和隐患。
主要检测服务器的承受能力,包括用户承受能力(多少用户同时玩基本不影响质量)、流量承受等。
2.2 为什么要压测
- 压测的目的就是通过压测(模拟真实用户的行为),测算出机器的性能(单台机器的QPS),从而推算出系统在承受指定用户数(100W)时,需要多少机器能支撑得住
- 压测是在上线前为了应对未来可能达到的用户数量的一次预估(提前演练),压测以后通过优化程序的性能或准备充足的机器,来保证用户的体验。
2.3 压测名词解释
2.3.1 压测类型解释
压测类型 | 解释 |
---|---|
压力测试(Stress Testing) | 也称之为强度测试,测试一个系统的最大抗压能力,在强负载(大数据、高并发)的情况下,测试系统所能承受的最大压力,预估系统的瓶颈 |
并发测试(Concurrency Testing) | 通过模拟很多用户同一时刻访问系统或对系统某一个功能进行操作,来测试系统的性能,从中发现问题(并发读写、线程控制、资源争抢) |
耐久性测试(Configuration Testing) | 通过对系统在大负荷的条件下长时间运行,测试系统、机器的长时间运行下的状况,从中发现问题(内存泄漏、数据库连接池不释放、资源不回收) |
2.3.2 压测名词解释
压测名词 | 解释 |
---|---|
并发(Concurrency) | 指一个处理器同时处理多个任务的能力(逻辑上处理的能力) |
并行(Parallel) | 多个处理器或者是多核的处理器同时处理多个不同的任务(物理上同时执行) |
QPS(每秒钟查询数量 Query Per Second) | 服务器每秒钟处理请求数量 (req/sec 请求数/秒 一段时间内总请求数/请求时间) |
事务(Transactions) | 是用户一次或者是几次请求的集合 |
TPS(每秒钟处理事务数量 Transaction Per Second) | 服务器每秒钟处理事务数量(一个事务可能包括多个请求) |
请求成功数(Request Success Number) | 在一次压测中,请求成功的数量 |
请求失败数(Request Failures Number) | 在一次压测中,请求失败的数量 |
错误率(Error Rate) | 在压测中,请求成功的数量与请求失败数量的比率 |
最大响应时间(Max Response Time) | 在一次压测中,从发出请求或指令系统做出的反映(响应)的最大时间 |
最少响应时间(Mininum Response Time) | 在一次压测中,从发出请求或指令系统做出的反映(响应)的最少时间 |
平均响应时间(Average Response Time) | 在一次压测中,从发出请求或指令系统做出的反映(响应)的平均时间 |
2.3.3 机器性能指标解释
机器性能 | 解释 |
---|---|
CUP利用率(CPU Usage) | CUP 利用率分用户态、系统态和空闲态,CPU利用率是指:CPU执行非系统空闲进程的时间与CPU总执行时间的比率 |
内存使用率(Memory usage) | 内存使用率指的是此进程所开销的内存。 |
IO(Disk input/ output) | 磁盘的读写包速率 |
网卡负载(Network Load) | 网卡的进出带宽,包量 |
2.3.4 访问指标解释
访问 | 解释 |
---|---|
PV(页面浏览量 Page View) | 用户每打开1个网站页面,记录1个PV。用户多次打开同一页面,PV值累计多次 |
UV(网站独立访客 Unique Visitor) | 通过互联网访问、流量网站的自然人。1天内相同访客多次访问网站,只计算为1个独立访客 |
2.4 如何计算压测指标
**
( 30000000*0.8 ) / (86400 * 0.2) ≈ 1389 (QPS)
- 假设:单台机器的的QPS是69,需要需要多少台机器来支撑?
1389 / 69 ≈ 20
3、常见的压测工具
3.1 ab
- 简介
ApacheBench 是 Apache 服务器自带的一个web压力测试工具,简称 ab。ab 又是一个命令行工具,对发起负载的本机要求很低,根据 ab 命令可以创建很多的并发访问线程,模拟多个访问者同时对某一 URL 地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说 ab 工具小巧简单,上手学习较快,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。
ab 属于一个轻量级的压测工具,结果不会特别准确,可以用作参考。
- 安装
# 在linux环境安装
sudo yum -y install httpd
- 用法
Usage: ab [options] [http[s]://]hostname[:port]/path
用法:ab [选项] 地址
选项:
Options are:
-n requests #执行的请求数,即一共发起多少请求。
-c concurrency #请求并发数。
-s timeout #指定每个请求的超时时间,默认是30秒。
-k #启用HTTP KeepAlive功能,即在一个HTTP会话中执行多个请求。默认时,不启用KeepAlive功能。
- 压测命令
# 使用ab压测工具,对百度的链接 请求100次,并发数1
ab -n 100 -c 1 https://www.baidu.com/
压测结果
~ >ab -n 100 -c 1 https://www.baidu.com/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking www.baidu.com (be patient).....done
Server Software: BWS/1.1
Server Hostname: www.baidu.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Document Path: /
Document Length: 227 bytes
Concurrency Level: 1
Time taken for tests: 9.430 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 89300 bytes
HTML transferred: 22700 bytes
Requests per second: 10.60 [#/sec] (mean)
Time per request: 94.301 [ms] (mean)
Time per request: 94.301 [ms] (mean, across all concurrent requests)
Transfer rate: 9.25 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 54 70 16.5 69 180
Processing: 18 24 12.0 23 140
Waiting: 18 24 12.0 23 139
Total: 72 94 20.5 93 203
Percentage of the requests served within a certain time (ms)
50% 93
66% 99
75% 101
80% 102
90% 108
95% 122
98% 196
99% 203
100% 203 (longest request)
Concurrency LevelTime taken for testsComplete requestsFailed requestsRequests per secondTime per requestTime per request
3.2 Locust
- 简介
是非常简单易用、分布式、python 开发的压力测试工具。有图形化界面,支持将压测数据导出。
- 安装
# pip3 安装locust
pip3 install locust
# 查看是否安装成功
locust -h
# 运行 Locust 分布在多个进程/机器库
pip3 install pyzmq
# webSocket 压测库
pip3 install websocket-client
- 用法
编写压测脚本 test.py
from locust import HttpLocust, TaskSet, task
# 定义用户行为
class UserBehavior(TaskSet):
@task
def baidu_index(self):
self.client.get("/")
class WebsiteUser(HttpLocust):
task_set = UserBehavior # 指向一个定义的用户行为类
min_wait = 3000 # 执行事务之间用户等待时间的下界(单位:毫秒)
max_wait = 6000 # 执行事务之间用户等待时间的上界(单位:毫秒)
- 启动压测
locust -f test.py --host=https://www.baidu.com
访问 http://localhost:8089 进入压测首页
Number of users to simulate 模拟用户数
Hatch rate (users spawned/second) 每秒钟增加用户数
点击 "Start swarming" 进入压测页面
压测界面右上角有:被压测的地址、当前状态、RPS、失败率、开始或重启按钮
性能测试参数
TypeNameRequestFailsMedianAverageMinMaxAverage sizeCurrent RPS
3.3 JMeter
- 简介
Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。
- 安装
- 用法
JMeter的功能过于强大,这里暂时不介绍用法,可以查询相关文档使用(参考文献中有推荐的教程文档)
3.4 云压测
3.4.1 云压测介绍
顾名思义就是将压测脚本部署在云端,通过云端对对我们的应用进行全方位压测,只需要配置压测的参数,无需准备实体机,云端自动给我们分配需要压测的云主机,对被压测目标进行压测。
云压测的优势:
- 轻易的实现分布式部署
- 能够模拟海量用户的访问
- 流量可以从全国各地发起,更加真实的反映用户的体验
- 全方位的监控压测指标
- 文档比较完善
当然了云压测是一款商业产品,在使用的时候自然还是需要收费的,而且价格还是比较昂贵的~
3.4.2 阿里云 性能测试 PTS
PTS(Performance Testing Service)是面向所有技术背景人员的云化测试工具。有别于传统工具的繁复,PTS以互联网化的交互,提供性能测试、API调试和监测等多种能力。自研和适配开源的功能都可以轻松模拟任意体量的用户访问业务的场景,任务随时发起,免去繁琐的搭建和维护成本。更是紧密结合监控、流控等兄弟产品提供一站式高可用能力,高效检验和管理业务性能。
阿里云同样还是支持渗透测试,通过模拟黑客对业务系统进行全面深入的安全测试。
3.4.3 腾讯云 压测大师 LM
通过创建虚拟机器人模拟多用户的并发场景,提供一整套完整的服务器压测解决方案
4、go-stress-testing go语言实现的压测工具
4.1 介绍
go-stress-testing 是go语言实现的简单压测工具,源码开源、支持二次开发,可以压测http、webSocket请求、私有rpc调用,使用协程模拟单个用户,可以更高效的利用CPU资源。
4.2 用法
- clone 项目源码运行的时候,需要将项目 clone 到 $GOPATH 目录下
- 支持参数:
Usage of ./go-stress-testing-mac:
-c uint
并发数 (default 1)
-n uint
请求数(单个并发/协程) (default 1)
-u string
压测地址
-d string
调试模式 (default "false")
-H value
自定义头信息传递给服务器 示例:-H 'Content-Type: application/json'
-data string
HTTP POST方式传送数据
-v string
验证方法 http 支持:statusCode、json webSocket支持:json
-p string
curl文件路径
-n-c-n
# 查看用法
./go-stress-testing-mac
# 使用请求百度页面
./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/
# 使用debug模式请求百度页面
./go-stress-testing-mac -c 1 -n 1 -d true -u https://www.baidu.com/
# 使用 curl文件(文件在curl目录下) 的方式请求
./go-stress-testing-mac -c 1 -n 1 -p curl/baidu.curl.txt
# 压测webSocket连接
./go-stress-testing-mac -c 10 -n 10 -u ws://127.0.0.1:8089/acc
- 完整压测命令示例
# 更多参数 支持 header、post body
go run main.go -c 1 -n 1 -d true -u 'https://page.aliyun.com/delivery/plan/list' \
-H 'authority: page.aliyun.com' \
-H 'accept: application/json, text/plain, */*' \
-H 'content-type: application/x-www-form-urlencoded' \
-H 'origin: https://cn.aliyun.com' \
-H 'sec-fetch-site: same-site' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-dest: empty' \
-H 'referer: https://cn.aliyun.com/' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'cookie: aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn' \
-data 'adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D'
- 使用 curl文件进行压测
curl是Linux在命令行下的工作的文件传输工具,是一款很强大的http命令行工具。
使用curl文件可以压测使用非GET的请求,支持设置http请求的 method、cookies、header、body等参数
I: chrome 浏览器生成 curl文件,打开开发者模式(快捷键F12),如图所示,生成 curl 在终端执行命令
II: postman 生成 curl 命令
生成内容粘贴到项目目录下的curl/baidu.curl.txt文件中,执行下面命令就可以从curl.txt文件中读取需要压测的内容进行压测了
# 使用 curl文件(文件在curl目录下) 的方式请求
go run main.go -c 1 -n 1 -p curl/baidu.curl.txt
4.3 实现
具体需求可以查看项目源码
项目目录结构
|____main.go // main函数,获取命令行参数
|____server // 处理程序目录
| |____dispose.go // 压测启动,注册验证器、启动统计函数、启动协程进行压测
| |____statistics // 统计目录
| | |____statistics.go // 接收压测统计结果并处理
| |____golink // 建立连接目录
| | |____http_link.go // http建立连接
| | |____websocket_link.go // webSocket建立连接
| |____client // 请求数据客户端目录
| | |____http_client.go // http客户端
| | |____websocket_client.go // webSocket客户端
| |____verify // 对返回数据校验目录
| | |____http_verify.go // http返回数据校验
| | |____websokcet_verify.go // webSocket返回数据校验
|____heper // 通用函数目录
| |____heper.go // 通用函数
|____model // 模型目录
| |____request_model.go // 请求数据模型
| |____curl_model.go // curl文件解析
|____vendor // 项目依赖目录
4.4 go-stress-testing 对 Golang web 压测
这里使用go-stress-testing对go server进行压测(部署在同一台机器上),并统计压测结果
- 申请的服务器配置
CPU: 4核 (Intel Xeon(Cascade Lake) Platinum 8269 2.5 GHz/3.2 GHz)
内存: 16G 硬盘: 20G SSD 系统: CentOS 7.6
go version: go1.12.9 linux/amd64
- go server
package main
import (
"log"
"net/http"
"runtime"
)
const (
httpPort = "8088"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU() - 1)
hello := func(w http.ResponseWriter, req *http.Request) {
data := "Hello, go-stress-testing! \n"
w.Header().Add("Server", "golang")
w.Write([]byte(data))
return
}
http.HandleFunc("/", hello)
err := http.ListenAndServe(":"+httpPort, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
- go_stress_testing 压测命令
./go-stress-testing-linux -c 100 -n 10000 -u http://127.0.0.1:8088/
- 压测结果
并发数 | go_stress_testing QPS |
---|---|
1 | 6394.86 |
4 | 16909.36 |
10 | 18456.81 |
20 | 19490.50 |
30 | 19947.47 |
50 | 19922.56 |
80 | 19155.33 |
100 | 18336.46 |
200 | 16813.86 |
从压测的结果上看:效果还不错,压测QPS有接近2W
5、压测工具的比较
5.1 比较
- | ab | locust | Jmeter | go-stress-testing | 云压测 |
---|---|---|---|---|---|
实现语言 | C | Python | Java | Golang | - |
UI界面 | 无 | 有 | 有 | 无 | 无 |
优势 | 使用简单,上手简单 | 支持分布式、压测数据支持导出 | 插件丰富,支持生成HTML报告 | 项目开源,使用简单,没有依赖,支持webSocket压测 | 更加真实的模拟用户,支持更高的压测力度 |
5.2 如何选择压测工具
这个世界上没有最好的,只有最适合的,工具千千万,选择一款适合你的才是最重要的
在实际使用中有各种场景,选择工具的时候就需要考虑这些:
明确你的目的,需要做什么压测、压测的目标是什么?
使用的工具你是否熟悉,你愿意花多大的成本了解它?
你是为了测试还是想了解其中的原理?
工具是否能支持你需要压测的场景
6、单台机器100w连接压测实战
6.1 说明
之前写了一篇文章,基于websocket单台机器支持百万连接分布式聊天(IM)系统(不了解这个项目可以查看上一篇或搜索一下文章),这里我们要实现单台机器支持100W连接的压测
目标:
- 单台机器能保持100W个长连接
- 机器的CPU、内存、网络、I/O 状态都正常
说明:
gowebsocket 分布式聊天(IM)系统:
之前用户连接以后有个全员广播,这里需要将用户连接、退出等事件关闭
服务器准备:
由于自己手上没有自己的服务器,所以需要临时购买的云服务器
压测服务器:
16台(稍后解释为什么需要16台机器)
CPU: 2核 内存: 8G 硬盘: 20G 系统: CentOS 7.6
被压测服务:
1台
CPU: 4核 内存: 32G 硬盘: 20G SSD 系统: CentOS 7.6
6.2 内核优化
- 修改程序最大打开文件数
被压测服务器需要保持100W长连接,客户和服务器端是通过socket通讯的,每个连接需要建立一个socket,程序需要保持100W长连接就需要单个程序能打开100W个文件句柄
# 查看系统默认的值
ulimit -n
# 设置最大打开文件数
ulimit -n 1040000
这里设置的要超过100W,程序除了有100W连接还有其它资源连接(数据库、资源等连接),这里设置为 104W
centOS 7.6 上述设置不生效,需要手动修改配置文件
vim /etc/security/limits.conf
这里需要把硬限制和软限制、root用户和所有用户都设置为 1040000
core 是限制内核文件的大小,这里设置为 unlimited
# 添加以下参数
root soft nofile 1040000
root hard nofile 1040000
root soft nofile 1040000
root hard nproc 1040000
root soft core unlimited
root hard core unlimited
* soft nofile 1040000
* hard nofile 1040000
* soft nofile 1040000
* hard nproc 1040000
* soft core unlimited
* hard core unlimited
注意:
/proc/sys/fs/file-max
如果file-max的值小于limits设置的值会导致系统重启以后无法登录
# file-max 设置的值参考
cat /proc/sys/fs/file-max
12553500
ulimit -n
6.3 客户端配置
0~65535(2^16-1)
这个数字是由于tcp协议决定的,tcp协议头部表示端口只有16位,所以最大值只有65535(如果每台机器多几个虚拟ip就能突破这个限制)
1024以下是系统保留端口,所以能使用的1024到65535
如果需要100W长连接,每台机器有 65535-1024 个端口, 100W / (65535-1024) ≈ 15.5,所以这里需要16台服务器
vim /etc/sysctl.conf
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216
sysctl -p
配置解释:
ip_local_port_rangetcp_memtcp_rmemtcp_wmem
6.4 准备
在被压测服务器上启动Server服务(gowebsocket)
查看被压测服务器的内网端口
登录上16台压测服务器,这里我提前把需要优化的系统做成了镜像,申请机器的时候就可以直接使用这个镜像(参数已经调好)
- 启动压测
./go_stress_testing_linux -c 62500 -n 1 -u ws://192.168.0.74:443/acc
62500*16 = 100W
-n 1
通过 gowebsocket服务器的http接口,实时查询连接数和项目启动的协程数
压测过程中查看系统状态
# linux 命令
ps # 查看进程内存、cup使用情况
iostat # 查看系统IO情况
nload # 查看网络流量情况
/proc/pid/status # 查看进程状态
6.5 压测数据
压测以后,查看连接数到100W,然后保持10分钟观察系统是否正常
观察以后,系统运行正常、CPU、内存、I/O 都正常,打开页面都正常
压测完成以后的数据
查看goWebSocket连接数统计,可以看到 clientsLen连接数为100W,goroutine数量2000008个,每个连接两个goroutine加上项目启动默认的8个。这里可以看到连接数满足了100W
62500*16=100W
- 记录内存使用情况,分别记录了1W到100W连接数内存使用情况
连接数 | 内存 |
---|---|
10000 | 281M |
100000 | 2.7g |
200000 | 5.4g |
500000 | 13.1g |
1000000 | 25.8g |
100W连接时的查看内存详细数据:
cat /proc/pid/status
VmSize: 27133804 kB
27133804/1000000≈27.1
如果需要如何减少内存使用可以参考 @Roy11568780 大佬给的解决方案
传统的golang中是采用的一个goroutine循环read的方法对应每一个socket。实际百万链路场景中这是巨大的资源浪费,优化的原理也不是什么新东西,golang中一样也可以使用epoll的,把fd拿到epoll中,检测到事件然后在协程池里面去读就行了,看情况读写分别10-20的协程goroutine池应该就足够了
至此,压测已经全部完成,单台机器支持100W连接已经满足~
7.常见问题
- Q: 压测过程中会出现大量 TIME_WAIT
A: 参考TCP四次挥手原理,主动关闭连接的一方会出现 TIME_WAIT 状态,等待的时长为 2MSL(约1分钟左右)
原因是:主动断开的一方回复 ACK 消息可能丢失,TCP 是可靠的传输协议,在没有收到 ACK 消息的另一端会重试,重新发送FIN消息,所以主动关闭的一方会等待 2MSL 时间,防止对方重试,这就出现了大量 TIME_WAIT 状态(参考: 四次挥手的最后两次)
8、总结
到这里压测总算完成,本次压测花费16元巨款。
单台机器支持100W连接是实测是满足的,但是实际业务比较复杂,还是需要持续优化~
本文通过介绍什么是压测,在什么情况下需要压测,通过单台机器100W长连接的压测实战了解Linux内核的参数的调优。如果觉得现有的压测工具不适用,可以自己实现或者是改造成属于自己的自己的工具。
9、参考文献
github 搜:link1st 查看项目 go-stress-testing