模拟ApiPost实现一个API压力测试程序(使用go)

386 阅读1分钟

用golang实现的一个简单的API压力测试程序,原理如下:使用协程和通道发送并发的网络请求,并统计相关数据。程序的命令行参数有并发请求数量、请求方法、请求URL。

package main

import (
   "flag"
   "fmt"
   "io"
   "net/http"
   "sort"
   "sync"
   "time"
)

var timeDistribution []time.Duration // 存储每个请求的用时

func main() {
   fmt.Println("start API stress test:")
   var concurrentCount int // 并发请求数量
   var method string       // 请求方法,Get或Post

   flag.IntVar(&concurrentCount, "c", 100, "concurrent request concurrentCount")
   flag.StringVar(&method, "m", "g", "request method: g is get, p is post")
   flag.Parse()

   args := flag.Args()
   if len(args) < 1 {
      fmt.Println("Usage: apic <URL>")
      return
   }
   url := args[0]

   success := make(chan struct{})
   var wg sync.WaitGroup
   var mu sync.Mutex
   start := time.Now()

   for i := 0; i < concurrentCount; i++ {
      wg.Add(1)
      go func() {
         defer wg.Done()
         sendRequest(url, success, method, &mu)
      }()
   }
   // 在协程里面调用wg.Wait()不让主协程阻塞,当所有goroutine完成后关闭success通道
   go func() {
      wg.Wait()
      close(success)
   }()
   successCount := 0
   for range success {
      successCount++
   }
   end := time.Now()
   var timePerRequest = time.Duration(float64(end.Sub(start)) / float64(concurrentCount))
   sort.Slice(timeDistribution, func(i, j int) bool {
      return timeDistribution[i] < timeDistribution[j]
   })
   length := len(timeDistribution)
   index50 := int(float64(length) * 0.5)
   index75 := int(float64(length) * 0.75)
   index90 := int(float64(length) * 0.90)
   index95 := int(float64(length) * 0.95)
   index99 := int(float64(length) * 0.99)

   fmt.Printf("success:%d total:%d\n", successCount, concurrentCount)                       // 统计成功数
   fmt.Printf("success rate: %.2f%%\n", float64(successCount)/float64(concurrentCount)*100) // 统计成功率
   fmt.Println("total time:", end.Sub(start))                                               // 统计总时间
   fmt.Println("time per request:", timePerRequest)                                         // 统计每个请求的平均时间
   fmt.Printf("requests per second: %.2f\n", float64(time.Second)/float64(timePerRequest))  // 统计每秒可以发送的请求数

   if length > 0 {
      fmt.Println("success request time distribution statistics:")
      fmt.Println("min request time:", timeDistribution[0])
      fmt.Println("max request time:", timeDistribution[length-1])
      fmt.Println("avg request time:", time.Duration(float64(sliceSum(timeDistribution))/float64(len(timeDistribution))))
      fmt.Println("50% requests in", timeDistribution[index50])
      fmt.Println("75% requests in", timeDistribution[index75])
      fmt.Println("90% requests in", timeDistribution[index90])
      fmt.Println("95% requests in", timeDistribution[index95])
      fmt.Println("99% requests in", timeDistribution[index99])
   }
}

func sendRequest(url string, success chan struct{}, method string, mu *sync.Mutex) {
   var resp *http.Response
   var err error
   start := time.Now()
   if method == "p" {
      resp, err = http.Post(url, "", nil)
   } else {
      resp, err = http.Get(url)
   }
   if err != nil {
      return
   }
   //关闭resp.Body,释放资源
   defer func(Body io.ReadCloser) {
      err := Body.Close()
      if err != nil {
         fmt.Println(err)
      }
   }(resp.Body)

   end := time.Now()
   mu.Lock()
   timeDistribution = append(timeDistribution, end.Sub(start))
   mu.Unlock()
   // 若请求成功向success通道写入数据
   if resp.StatusCode == http.StatusOK {
      success <- struct{}{}
   }
}

func sliceSum(s []time.Duration) time.Duration {
   var sum time.Duration = 0
   for _, duration := range s {
      sum += duration
   }
   return sum
}

使用下面的命令可以将上面的程序编译成可执行程序

go build -o apic main.go

如果要将程序变成系统命令,可以使用下面的命令

sudo cp apic /usr/local/bin

程序的执行示例如下

image.png