您现在的位置是:首页 > 文章详情

go web压测工具实现

日期:2018-10-18点击:255

这篇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,欢迎修改完善

原文链接:https://yq.aliyun.com/articles/655690
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章