go web压测工具实现
这篇Go实现单机压测工具博客分以下几个模块进行讲解,为了更加清楚的知道一个分布式Web压测实现,我们从单机单用户 -> 单机多用户 -> 分布式逐步实现。
(1)什么是web压力测试?
(2)压力测试中几个重要指标
(3)Go语言实现单机单用户压测
(4)GO语言实现单机多用户压测
(5)Go语言实现分布式压测
(6)相关参考资料
一、什么是web压力测试?
简单的说是测试一个web网站能够支撑多大的请求(也就是web网站的最大并发)
二、压力测试的几个重要指标
1)指标有哪些?
2)指标含义详解
https://blog.csdn.net/adparking/article/details/45673315
三、单机单用户压测Go实现
1)要知道的几个概念
并发连接数:
理解:并发连接数不等于并发数,真实的并发数只能在服务器中计算出来,这边的并发数等于处在从请求发出去,到收到服务器信息的这状态的个数总和。
总请求次数
响应时间
平均响应时间
成功次数
失败次数
2)代码实现
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"sync"
"time"
)
var (
SBCNum int // 并发连接数
QPSNum int // 总请求次数
RTNum time.Duration // 响应时间
RTTNum time.Duration // 平均响应时间
SuccessNum int // 成功次数
FailNum int // 失败次数
BeginTime time.Time // 开始时间
SecNum int // 秒数
RQNum int // 最大并发数,由命令行传入
Url string // url,由命令行传入
controlNum chan int // 控制并发数量
)
var mu sync.Mutex // 必须加锁
func init() {
if len(os.Args) != 3 {
log.Fatal("请求次数 url")
}
RQNum, _ = strconv.Atoi(os.Args[1])
controlNum = make(chan int, RQNum)
Url = os.Args[2]
}
func main() {
go func() {
for range time.Tick(1 * time.Second) {
SecNum++
fmt.Printf("并发数:%d,请求次数:%d,平均响应时间:%s,成功次数:%d,失败次数:%d\n",
len(controlNum), SuccessNum+FailNum, RTNum/(time.Duration(SecNum)*time.Second), SuccessNum, FailNum)
}
}()
requite()
}
func requite() {
for {
controlNum <- 1
go func(c chan int) {
var tb time.Time
var el time.Duration
for {
tb = time.Now()
_, err := http.Get(Url)
if err == nil {
el = time.Since(tb)
mu.Lock() // 上锁
SuccessNum++
RTNum += el
mu.Unlock() // 解锁
} else {
mu.Lock() // 上锁
FailNum++
mu.Unlock() // 解锁
}
time.Sleep(1 * time.Second)
}
<- c
}(controlNum)
time.Sleep(45 * time.Millisecond)
}
}
四、单机多用户压测Go实现
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"sync"
"time"
)
var (
BeginTime time.Time // 开始时间
SecNum int // 秒数
RQNum int // 最大并发数,由命令行传入
Url string // url,由命令行传入
userNum int // 用户数
)
var users []User
type User struct {
UserId int // 用户id
SBCNum int // 并发连接数
QPSNum int // 总请求次数
RTNum time.Duration // 响应时间
RTTNum time.Duration // 平均响应时间
SuccessNum int // 成功次数
FailNum int // 失败次数
mu sync.Mutex
}
func (u *User) request(url string) {
var tb time.Time
var el time.Duration
for i := 0;i < u.QPSNum;i++ {
u.SBCNum++
go func(u *User) {
for {
tb = time.Now()
_, err := http.Get(Url)
if err == nil {
el = time.Since(tb)
u.mu.Lock() // 上锁
u.SuccessNum++
u.RTNum += el
u.mu.Unlock() // 解锁
} else {
u.mu.Lock() // 上锁
u.FailNum++
u.mu.Unlock() // 解锁
}
time.Sleep(1 * time.Second)
}
}(u)
}
}
func (u *User) show() {
fmt.Printf("用户id:%d,并发数:%d,请求次数:%d,平均响应时间:%s,成功次数:%d,失败次数:%d\n",
u.UserId,
u.SBCNum,
u.SuccessNum + u.FailNum,
u.RTNum/(time.Duration(SecNum)*time.Second),
u.SuccessNum,
u.FailNum)
}
func showAll(us []User) {
uLen := len(us)
var SBCNum int // 并发连接数
var RTNum time.Duration // 响应时间
var SuccessNum int // 成功次数
var FailNum int // 失败次数
for i := 0;i < uLen;i++ {
SBCNum += us[i].SBCNum
SuccessNum += us[i].SuccessNum
FailNum += us[i].FailNum
RTNum += us[i].RTNum
us[i].show()
}
fmt.Printf("并发数:%d,请求次数:%d,平均响应时间:%s,成功次数:%d,失败次数:%d\n",
SBCNum,
SuccessNum+FailNum,
RTNum/(time.Duration(SecNum)*time.Second),
SuccessNum,
FailNum)
fmt.Println()
}
func init() {
if len(os.Args) != 4 {
log.Fatal("用户数 请求次数 url")
}
userNum, _ = strconv.Atoi(os.Args[1])
RQNum, _ = strconv.Atoi(os.Args[2])
Url = os.Args[3]
users = make([]User, userNum)
}
func main() {
go func() {
for range time.Tick(2 * time.Second) {
SecNum += 2
showAll(users)
}
}()
for range time.Tick(1 * time.Second) {
requite()
}
}
func requite() {
c := make(chan int)
temp := 0
for i := 0;i < userNum;i++ {
if RQNum % userNum != 0 && i < RQNum % userNum {
temp = 1
} else {
temp = 0
}
users[i].UserId = i
users[i].QPSNum = RQNum / userNum + temp
go users[i].request(Url)
time.Sleep(45 * time.Millisecond)
}
<- c // 阻塞
}
五、分布式压测Go
分主节点和从节点,现在分别实现以下功能
1)主节点功能
收集从节点的压测信息
显示压测信息
2)从节点功能
将压测信息发送给主节点
3)整个工作原理
一个主节点启动,设置监听端口,使用TCP方式,启动若干个从节点,每个从节点通过IP+端口连接到这个主节点,之后主节点记录连接上来的从节点信息。从节点将相关信息发往主节点,主节点在设定的时间里显示信息。
代码实现:
主节点代码实现
package main
import (
"log"
"net"
"time"
"os"
"encoding/json"
"fmt"
)
var ip string
var port string
var slaves []*slave
type slave struct {
UserId string
SBCNum int // 并发连接数
QPSNum int // 总请求次数
RTNum time.Duration // 响应时间
RTTNum time.Duration // 响应时间
SecNum int // 时间
SuccessNum int // 成功次数
FailNum int // 失败次数
Url string
conn net.Conn
}
func (s *slave) Run() {
var v interface{}
buf := make([]byte, 1024)
for {
n, err := s.conn.Read(buf)
if err != nil {
log.Println(err)
break
}
err = json.Unmarshal(buf[:n], &v)
if err != nil {
log.Println(err)
continue
}
s.SBCNum = int(v.(map[string]interface{})["SBCNum"].(float64)) // 并发连接数
s.RTNum = time.Duration(v.(map[string]interface{})["RTNum"].(float64)) // 响应时间
s.SuccessNum = int(v.(map[string]interface{})["SuccessNum"].(float64)) //SuccessNum int // 成功次数
s.FailNum = int(v.(map[string]interface{})["FailNum"].(float64)) //FailNum int // 失败次数
s.SecNum = int(v.(map[string]interface{})["SecNum"].(float64))
}
}
func init() {
if len(os.Args) != 3 {
log.Fatal(os.Args[0] + " ip port")
}
ip = os.Args[1]
port = os.Args[2]
}
func main() {
s, err := net.Listen("tcp", ip + ":" + port)
if err != nil {
log.Fatal(err)
}
defer s.Close()
buf := make([]byte, 128)
fmt.Println("Run...")
go func() {
for range time.Tick(2 * time.Second) {
show(slaves)
}
}()
for {
conn, err := s.Accept()
if err != nil {
log.Println(err)
continue
}
n, err := conn.Read(buf)
tempC := slave{conn:conn,UserId:conn.RemoteAddr().String(), Url:string(buf[:n])}
go tempC.Run()
slaves = append(slaves, &tempC)
}
}
func show(clients []*slave) {
if len(clients) == 0 {
return
}
temp := slave{}
num := 0
for _, client := range clients {
if client.SecNum == 0 {
continue
}
num++
fmt.Printf("用户id:%s,url: %s,并发数:%d,请求次数:%d,平均响应时间:%s,成功次数:%d,失败次数:%d\n",
client.UserId,
client.Url,
client.SBCNum,
client.SuccessNum + client.FailNum,
client.RTNum / (time.Duration(client.SecNum) * time.Second),
client.SuccessNum,
client.FailNum)
temp.SBCNum += client.SBCNum
temp.RTNum += client.RTNum / (time.Duration(client.SecNum) * time.Second)
temp.SecNum += client.SecNum
temp.SuccessNum += client.SuccessNum
temp.FailNum += client.FailNum
}
if num == 0 {
return
}
fmt.Printf("并发数:%d,请求次数:%d,平均响应时间:%s,成功次数:%d,失败次数:%d\n",
temp.SBCNum,
temp.SuccessNum + temp.FailNum,
temp.RTNum / time.Duration(num),
temp.SuccessNum,
temp.FailNum)
fmt.Println()
}
func heartbeat(clients []slave) []slave { // 标记耦合
tempC := []slave{}
for _, client := range clients {
_, err := client.conn.Write([]byte(""))
if err == nil { // 删除
tempC = append(tempC, client)
}
}
return tempC
}
从节点
package main
import (
"net"
"github.com/lunny/log"
"encoding/json"
"time"
"os"
"net/http"
"sync"
"strconv"
"fmt"
)
type master struct {
ip string
port string
conn net.Conn
}
var (
SBCNum int // 并发连接数
QPSNum int // 总请求次数
RTNum time.Duration // 响应时间
RTTNum time.Duration // 平均响应时间
SuccessNum int // 成功次数
FailNum int // 失败次数
SecNum int
mt master
err error
mu sync.Mutex // 必须加锁
RQNum int // 最大并发数,由命令行传入
Url string // url,由命令行传入
)
func init() {
if len(os.Args) != 5 {
log.Fatalf("%s 并发数 url ip port", os.Args[0])
}
RQNum, err = strconv.Atoi(os.Args[1])
if err != nil {
log.Println(err)
}
Url = os.Args[2]
mt.ip = os.Args[3]
mt.port = os.Args[4]
}
func main() {
mt.conn, err = net.Dial("tcp", mt.ip + ":" + mt.port)
if err != nil {
log.Fatal(err)
}
fmt.Println("连接服务器成功。。。")
_, err = mt.conn.Write([]byte(Url))
if err != nil {
log.Println(err)
}
go func() {
for range time.Tick(1 * time.Second) {
sendToMaster(mt, map[string]interface{}{
"SBCNum": SBCNum, // 并发连接数
"RTNum": RTNum, // 响应时间
"SecNum": SecNum, // 时间
"SuccessNum": SuccessNum, // 成功次数
"FailNum": FailNum, // 失败次数
})
}
}()
go func() {
for range time.Tick(1 * time.Second) {
SecNum++
}
}()
requite(RQNum, Url)
}
func requite(RQNum int, url string) {
c := make(chan int)
for i := 0;i < RQNum;i++ {
SBCNum = i + 1
go func(url string) {
var tb time.Time
var el time.Duration
for {
tb = time.Now()
_, err := http.Get(url)
if err == nil {
el = time.Since(tb)
mu.Lock() // 上锁
SuccessNum++
RTNum += el
mu.Unlock() // 解锁
} else {
mu.Lock() // 上锁
FailNum++
mu.Unlock() // 解锁
}
time.Sleep(1 * time.Second)
}
}(url)
time.Sleep(45 * time.Millisecond)
}
<- c // 阻塞
}
func sendToMaster(mt master, data map[string]interface{}) {
r, err := json.Marshal(data)
if err != nil {
log.Println(err)
}
_, err = mt.conn.Write(r)
if err != nil {
log.Println(err)
os.Exit(1)
}
}
参考链接:
压测指标概念
(1)https://www.cnblogs.com/shijingjing07/p/6507317.html
(2)https://blog.csdn.net/adparking/article/details/45673315
github链接:https://github.com/laijinhang/WebRequest
有出错的地方,请指出,代码已上传到github,欢迎修改完善

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Python 数据类型 list(2)
对list的操作 向list中插入一个元素 list.append(x) 在末尾增加 >>> all_users = ["hiekay","github"] >>> all_users.append("io") >>> all_users ['hiekay', 'github', 'io'] list.insert(i,x) : 可以在任何位置增加一个元素 list.insert(i,x),将新的元素x 插入到原list中的list[i]前面 如果i==len(list),意思是在后面追加,就等同于list.append(x) 实验: >>> all_users ['hiekay', 'github', 'io'] >>> all_users.insert("python") #list.insert(i,x),要求有两个参数,少了就报错 Traceback (most recent call last): File "<stdin>", line 1, in <module&g...
-
下一篇
IntelliJ IDEA个人配置
主题 字体 推荐DejaVu Sans Mono,这个字体绝对爽翻 第二推荐Courier 或者 Courier New IDEA应用字体 代码字体 预览 常用插件 Alibaba Java Coding Guidelines阿里巴巴编程规范,可以检查自己的java命名规范编程规范以及bug,并会给出相应的解决方案 Grep Console对控制台输出的不同级别的日志进行上色,比如info级别是黑色,WARN级别是黄色,ERROR是橙色。不用找日志把眼睛找瞎了。 Kotlin项目中使用到了Kotlin可以安装 Lombok Plugin简化重复的java代码 Translation翻译
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- MySQL数据库在高并发下的优化方案
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能