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的压测工具和其他测试工具等,敬请期待
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容