前言
- 前一篇文已經(jīng)簡單講解怎么通過goroutines的能力編寫并發(fā)http壓測腳本降狠,但前文有提到過,主線程為了等待goroutine都運行完畢溜畅,不得不在程序的末尾使用time.Sleep() 來睡眠一段時間捏卓,等待其他線程充分運行。對于簡單的代碼,100個for循環(huán)可以在1秒之內(nèi)運行完畢怠晴,time.Sleep() 也可以達到想要的效果遥金,但是對于大多數(shù)真實業(yè)務(wù)和工作場景來說,1秒肯定會不夠的蒜田,并且大部分時候我們都無法預(yù)知for循環(huán)內(nèi)代碼運行時間的長短稿械。這時候就不能使用time.Sleep() 來完成等待操作了,這里我們就可以使用golang的一個類似于計數(shù)器的組件sync.WaitGroup
sync.WaitGroup模塊
- WaitGroup 對象內(nèi)部有一個計數(shù)器冲粤,最初從0開始美莫,它有三個方法:Add(), Done(), Wait() 用來控制計數(shù)器的數(shù)量。Add(n) 把計數(shù)器設(shè)置為n 梯捕,Done() 每次把計數(shù)器-1 厢呵,wait() 會阻塞代碼的運行,直到計數(shù)器地值減為0傀顾,結(jié)合goroutines使用
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 20; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
可以執(zhí)行查看一些輸出
go run synctest.go
1
5
2
3
4
7
6
19
15
0
10
14
18
16
12
8
11
13
17
9
- 這里首先把wg 計數(shù)設(shè)置為20襟铭, 每個for循環(huán)運行完畢都把計數(shù)器減一,主函數(shù)中使用Wait() 一直阻塞短曾,直到wg為0,也就是所有的20個for循環(huán)都運行完畢寒砖,WaitGroup 通過計數(shù)器的方式很好地控制了gorountines和主線程的執(zhí)行關(guān)系
sync.WaitGroup模塊編寫場景化的壓測腳本
- 有了sync.WaitGroup提供的能力,我們可以更好的基于一些場景去編寫壓測腳本
- 有這樣子的一個服務(wù)器
#! /usr/bin/env
#coding=utf-8
import socket
import json
import requests
from flask import Flask, request,jsonify,g
app = Flask(__name__)
filename='demo.txt'
@app.route('/apisetdata', methods=['POST'])
def setdata():
msg=request.json["msg"]
print(msg)
with open(filename, 'w') as f:
f.write(str(msg))
f.close()
resp=jsonify({"code":200,"state":"set msg ok","msg":msg})
resp.status_code=200
return resp
@app.route('/apigetdata', methods=['GET'])
def getdata():
f = open(filename, 'r')
msg=f.read()
resp=jsonify({"code":200,"state":"get msg ok","msg":msg})
resp.status_code=200
return resp
if __name__ == "__main__":
app.run(debug=True)
- msg保存著demo.txt的內(nèi)容嫉拐,通過apigetdata接口可以獲取內(nèi)容哩都,通過apisetdata接口可以修改demo.txt的內(nèi)容,我們現(xiàn)在需要對修改demo.txt的內(nèi)容后查詢這個場景進行壓測椭岩,這樣我們就可以獲得這樣的壓測腳本
package main
import (
"fmt"
"net/http"
"sync"
"time"
"bytes"
"encoding/json"
"io/ioutil"
simplejson "github.com/bitly/go-simplejson"
)
var (
success = 0
failure = 0
useTime = 0.0 //記錄請求成功失敗數(shù)和使用時間
)
var (
num =100 //要發(fā)送的請求數(shù)
con =100 //并發(fā)數(shù)
)
type HttpData struct {
Msg string `json:"msg"`
}
var wg sync.WaitGroup //創(chuàng)建一個計數(shù)器
func dotest(num int) { //場景化請求方法茅逮,在里面可以自定義需要編輯的內(nèi)容
defer wg.Done() //進入到方法后計算器-1璃赡,標記創(chuàng)建了一個goroutine
no := 0
ok := 0
url := "http://127.0.0.1:5000/apisetdata"
url2:= "http://127.0.0.1:5000/apigetdata"
contentType := "application/json;charset=utf-8"
var httpdata HttpData
httpdata.Msg = "terrychow"
b ,err := json.Marshal(httpdata)
if err != nil {
fmt.Println("json format error:", err)
return
}
body := bytes.NewBuffer(b)
for i := 0; i < num; i++ {
//修改內(nèi)容部分
resp, err := http.Post(url, contentType, body) //通過apisetdata接口修改內(nèi)容
if err != nil {
no += 1
fmt.Println("error failed:", err)
continue
}
defer resp.Body.Close()
//讀取內(nèi)容部分
resp2, err := http.Get(url2) //通過apigetdata接口獲取內(nèi)容
if err != nil {
no += 1
fmt.Println("error failed:", err)
continue
}
defer resp2.Body.Close()
body, err := ioutil.ReadAll(resp2.Body)
res, err := simplejson.NewJson([]byte(string(body)))
getmsg, err := res.Get("msg").String()
if resp.StatusCode != 200 {
no += 1
continue
}
if getmsg!=httpdata.Msg{ //斷言修改的內(nèi)容
no += 1
continue
}
ok += 1
continue
}
success += ok
failure += no
}
// 主函數(shù)
func main() {
startTime := time.Now().UnixNano()
// 并發(fā)開始
for i := 0; i < con; i++ {
wg.Add(1)
go dotest(num/con)
}
// fmt.Println("主程序開始wait")
wg.Wait()
endTime := time.Now().UnixNano()
useTime = float64(endTime-startTime) / 1e9
// 輸出結(jié)果
fmt.Println()
fmt.Println("Complete requests:", success)
fmt.Println("Failed requests:", failure)
// fmt.Println("SuccessRate:", fmt.Sprintf("%.2f", ((success/total)*100.0)), "%")
fmt.Println("UseTime:", fmt.Sprintf("%.4f", useTime), "s")
fmt.Println("場景每秒處理數(shù) sps:", fmt.Sprintf("%.4f", float64(num)/useTime))
fmt.Println("請求每秒處理數(shù) qps:", fmt.Sprintf("%.4f", float64(num*2)/useTime))
}
- 執(zhí)行一下查看效果
go run pertestgo.go
Complete requests: 100
Failed requests: 0
UseTime: 0.1738 s
場景每秒處理數(shù) sps: 575.4601
請求每秒處理數(shù) qps: 1150.9202
- 這里的壓測腳本主要強調(diào)場景判哥,像ab等壓測工具,很多時候都是只有單接口的并發(fā)碉考,對于復(fù)雜接口和做一些自定義斷言的時候塌计,就相對比較復(fù)雜,類似python的locust就是按照場景化寫壓測腳本的方式實現(xiàn)侯谁,這里也是運用了同樣的思想
小結(jié)
- 希望本文對于同學(xué)們在使用golang進行壓測的時候能夠起到作用锌仅,接下來的文章還會講述幾個golang的壓測工具和其他測試工具等,敬請期待