hey是一款go开发的压测工具,使用命令行模式,不支持图形界面展示,只支持单用户,不支持分布式,使用简单,学习成本低,只需要使用hey --help,会用几个参数就行。
安装也很简单
如果是mac 直接 brew install hey
如果是linux,从https://github.com/rakyll/hey?tab=readme-ov-file 这里下载可执行文件,不需安装。
使用举例:
hey -n 10000 -c 100 -m GET -H "Content-Type:application/json" "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJEYXRhIjoiSHJic2wzeXk5Nzl6SnRBcFlLSUVRV3RqblF0WG9FVklnbEFjeWRBa0l3RkstbC15R0FsMk9HVnNISzJWV191SWpPa2Q5Q2VYSHNjTWFuRE53a2Y2SjZJR01uaUJ3SktkVTJBa1hidEN6VGhlNVc1anVkUDczdlRsbTl1VVFnPT0iLCJWZXJzaW9uIjoxLCJleHAiOjE3MTgyNzk2MTZ9.j-hUDHvokHvDB27llDElEUDIfBk0aHEuwkHalFGKI64" "https://segmentfault.com/api/instance/list?page=1&page_size=10"
使用100个协程并发,总共发送1万个请求,GET方式,设置两个header,请求url最好使用引号括起来,避免不识别参数,url必须放到最后。
主要的参数如下:
-n 总请求数
-c 并行worker数,hey使用go作为开发语言,这个选项指定了发出并行请求的工作协程数量。
**本文精华
-q 限制每个worker的qps数量。
比如 -n 100 -c 2 -q 5
启用2个协程,给服务发送请求,总请求数100个,每个协程限制qps不超过5,总的qps不超过2*5=10。
该参数并不能精确控制qps,他内部实现逻辑是通过一个定时器控制每个请求之后间隔时间。
比如设置qps=100,请求间隙是 1e6 / 100 = 1e4微妙,假如接口耗时100ms,那么每次请求相当于100ms+10000微妙=110ms。实际qps是 1000/110 = 9 **
-H 设置请求header,可以多次指定,hey在内部使用数组来接收。例如:
-H “Content-type:application/json” -H "Authorization:Bearer eyJhbGc"
-m 指定method类型,GET,POST等
-d '{"username":"subyun","password":"c3ViWGl"}' 指定post请求的参数,配合 -H "Content-type:application/json" 一起使用。
- 返回结果包含
平均rt,99分位,qps,成功数,失败数量会按不同类型区分,hey是根据http返回码区分是否成功的,如果业务上错误的时候也返回200,hey就认为是成功。不支持语义自定义。
整个项目大量使用了无缓冲channel来实现不同协程之间的同步。
自定义开发
hey的缺点很明显,每个并发的请求参数都完全一样,自己写了一个简单的http请求,每个请求都带自己的独特参数
shell版本。register.sh
register.sh -n 10
指定10个进程并发请求
#!/bin/bash
# 接收命令行参数 -n
while getopts "n:" opt
do
case $opt in
n)
num=$OPTARG
;;
?)
echo "there is unrecognized parameter."
exit 1
;;
esac
done
for((i=1;i<=${num};i++));
do
curl -X POST -d '{"username":"sifou${i}","password":"e***","phone":"${i}1234","verify_code":"615694"}' -H "Content-Type: application/json" "https://segmentfault.com/api/auth/register" & >> register.txt
done
# curl 命令加&后台并发执行,使用wait等待所有命令执行完,主进程才退出
wait
这种方式每个请求都开辟一个进程,比较费资源
使用下面python,使用线程并发。
./register.py 10
指定10个线程并发请求
#!/usr/bin/python
import requests
import time
import threading
import random
import redis
import sys
from redis.sentinel import Sentinel
import time
def generate_virtual_phone_number():
# 定义手机号码的前缀范围
prefixes = [13, 14, 15, 16, 17, 18, 19]
# 随机选择一个前缀
prefix = random.choice(prefixes)
# 生成中间的四位数字,避免使用0000和1111等特殊组合
middle_four = random.randint(1000, 9999)
# 生成最后的五位数字,同样避免使用特殊组合
last_six = random.randint(10000, 99999)
# 组合成完整的手机号码
phone_number = f"+86{prefix}{middle_four:04}{last_six:05}"
return phone_number
class myThread (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
global correct, err
#print("Starting " + self.name)
url = "https://segmentfault.com/api/auth/register"
#url = "http://127.0.0.1:8888/api/auth/register"
headers = {'content-type': 'application/json'}
phone = generate_virtual_phone_number()
verify_code = "123456"
set_verify_code(phone, verify_code, self.threadID)
requestData = {"username":"sifou"+ str(self.threadID),"password":"eGlZdW=","phone":phone,"verify_code":verify_code}
print(requestData)
ret = requests.post(url, json=requestData, headers=headers, timeout=(5, 10))
threadLock.acquire()
print(str(self.threadID) + " " + ret.text)
if ret.status_code == 200:
correct = correct+1
else:
err = err+1
threadLock.release()
#print("Exiting " + self.name)
def set_verify_code(phone, code, threadID):
conf = {
'sentinel': [('10.11.11.11', 26379), ('10.22.22.22', 26379)],
'master_group_name': 'redis-ha',
'connection_conf': {
#'socket_timeout': 0.5,
#'retry_on_timeout': True,
#'socket_keepalive': True,
#'max_connections': 10,
'db': 0,
'encoding': 'utf8',
'decode_responses': True,
}
}
sentinel = Sentinel(conf['sentinel'], **conf['connection_conf'])
sentinel.discover_master(conf['master_group_name'])
cli = sentinel.master_for(conf['master_group_name'], password='*****', port=6379)
ret = cli.setex("register:" + phone, 60, code)
print(str(threadID) + " " + str(ret))
#print("redisget:" + cli.get("register:" + phone))
threadLock = threading.Lock()
threads = []
correct = 0
err = 0
start = time.perf_counter()
for i in range(1, int(sys.argv[1])):
id = random.randint(1, 9999999)
thread = myThread(id, "Thread-" + str(i))
thread.start()
threads.append(thread)
for t in threads:
t.join()
end = time.perf_counter()
print("correct: " + str(correct))
print('err: ' + str(err))
print('average: ' + str((end - start) / sys.argv[1]))
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。