golang分层测试之http压测脚本编写(2)

前言

  • 前一篇文已经简单讲解怎么通过goroutines的能力编写并发http压测脚本,但前文有提到过,主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep() 来睡眠一段时间,等待其他线程充分运行。对于简单的代码,100个for循环可以在1秒之内运行完毕,time.Sleep() 也可以达到想要的效果,但是对于大多数真实业务和工作场景来说,1秒肯定会不够的,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用time.Sleep() 来完成等待操作了,这里我们就可以使用golang的一个类似于计数器的组件sync.WaitGroup

sync.WaitGroup模块

  • WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0,结合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()
}

可以执行查看一些输出

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 计数设置为20, 每个for循环运行完毕都把计数器减一,主函数中使用Wait() 一直阻塞,直到wg为0,也就是所有的20个for循环都运行完毕,WaitGroup 通过计数器的方式很好地控制了gorountines和主线程的执行关系

sync.WaitGroup模块编写场景化的压测脚本

  • 有了sync.WaitGroup提供的能力,我们可以更好的基于一些场景去编写压测脚本
  • 有这样子的一个服务器
#! /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的内容,通过apigetdata接口可以获取内容,通过apisetdata接口可以修改demo.txt的内容,我们现在需要对修改demo.txt的内容后查询这个场景进行压测,这样我们就可以获得这样的压测脚本
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 //记录请求成功失败数和使用时间
)

var (
    num =100 //要发送的请求数
    con =100 //并发数
)

type HttpData struct {

    Msg string `json:"msg"`

}

var wg sync.WaitGroup //创建一个计数器



func dotest(num int) { //场景化请求方法,在里面可以自定义需要编辑的内容

    defer wg.Done() //进入到方法后计算器-1,标记创建了一个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++ {

        //修改内容部分
        resp, err := http.Post(url, contentType, body) //通过apisetdata接口修改内容

        if err != nil {
            no += 1
            fmt.Println("error failed:", err)
            continue 
        }

        defer resp.Body.Close()

        //读取内容部分
        resp2, err := http.Get(url2) //通过apigetdata接口获取内容
        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{ //断言修改的内容
            no += 1
            continue        
        }

        ok += 1
        continue
    }

    success += ok
    failure += no

}       

// 主函数
func main() {
    startTime := time.Now().UnixNano()

    // 并发开始
    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
    // 输出结果
    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("场景每秒处理数 sps:", fmt.Sprintf("%.4f", float64(num)/useTime))
    fmt.Println("请求每秒处理数 qps:", fmt.Sprintf("%.4f", float64(num*2)/useTime))

}
  • 执行一下查看效果
go run pertestgo.go
Complete requests: 100
Failed requests: 0
UseTime: 0.1738 s
场景每秒处理数 sps: 575.4601
请求每秒处理数 qps: 1150.9202
  • 这里的压测脚本主要强调场景,像ab等压测工具,很多时候都是只有单接口的并发,对于复杂接口和做一些自定义断言的时候,就相对比较复杂,类似python的locust就是按照场景化写压测脚本的方式实现,这里也是运用了同样的思想

小结

  • 希望本文对于同学们在使用golang进行压测的时候能够起到作用,接下来的文章还会讲述几个golang的压测工具和其他测试工具等,敬请期待

你可能感兴趣的:(golang分层测试之http压测脚本编写(2))