目录


自助压测系统的局限

公司已经有基于集群实现的自助压测系统,自助压测系统压的是整个集群, 不方便对单台进行压测,有时候只压某一台即可定位问题。

为什么不使用Java

压测需要模拟多线程并发,在 JVM 中线程上下文的切换是很慢的使用操作系统的 threads 的最大能力一般在万级别,主要消耗是在上下文切换的延迟。

Java 只允许数千级别的 threads。

Goroutines是一种由 Go 运行时系统管理的“极轻量级线程”。使用 Goroutines 可以创建异步并行的项目,执行这些并行任务要比执行对应的串行任务快很多。

Goroutines 通常在初始化时仅需要分配 2KB 的栈空间,而线程通常需要占用 1MB。因此,Goroutines 远比线程要更轻量级。

新建的一个 Goroutine 实际只占用 4KB 的栈空间。一个栈只占用 4KB,1GB 的内存可以创建 250 万个 Goroutine。

总结一句话:go可以通过有限的资源实现很大的并发。

基于go的简单实现
  1. 通过命令行传参控制并发数
  2. 基于go的http.Client发送http json请求
  3. 支持传cookie
  4. 通过sync.WaitGroup等待所有Goroutines执行完毕
  5. 解析response,判断错误code

下载安装go,并通过go version判断安装成功

sudo tar -C/usr/local -xzfgo1.13.linux-amd64.tar.gzexport PATH=$PATH:/usr/local/go/binsource ~/.profilego version

代码实现 ,通过 go build main.go 编译后,执行命令并通过-P=线程数 传参: ./main -P=999

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strings"
	"sync"
	"time"
)

var count int

 
var urlStr = "http://mall.bilibili.com/mall-c/cart/all?build=0&operationType=0"
 

func makeTimestamp() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}

func main() {
	a := makeTimestamp()
	callRemote()
	b := makeTimestamp()
	fmt.Printf("cost = %d ", b-a)
}

func callRemote() {
	flag.IntVar(&count, "P", 10, "default value")
	flag.Parse()
	fmt.Println("finally count = ", count)
	var wg sync.WaitGroup
	wg.Add(count)

	jar, _ := cookiejar.New(nil)
	str := "{\"shopId\":2233}"
	http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Minute * 30
	var cookies []*http.Cookie
	var cookie = &http.Cookie{
		Name:  "SESSDATA",
		Value: "28cd1772%2C1632572281%2C68d45%2A31",
	}
	cookies = append(cookies, cookie)
	u, _ := url.Parse(urlStr)
	jar.SetCookies(u, cookies)
	client := &http.Client{
		Jar: jar,
	}
	for i := 0; i < count; i++ {
		go invokeRemote(client, str, wg.Done)
	}
	wg.Wait()
}

func invokeRemote(client *http.Client, str string, done func()) {
	defer done()
	req, _ := http.NewRequest("POST", urlStr, strings.NewReader(str))
	req.Header.Add("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
	}
	if resp == nil {
		// handle error
		return
	}
	if resp.Body == nil {
		// handle error
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error
		return
	}
	result := string(body)
	var code = getCode(result)
	if code != 0 {
		fmt.Println("error happend ", code)
	}
}

type Message struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func getCode(jsonStr string) int {
	var message Message
	if err := json.Unmarshal([]byte(jsonStr), &message); err == nil {
		return message.Code
	}
	return -99
}