Go 通过实例入门 Golang 老王

laofo · 2017年06月12日 · 5 次阅读

如果想学会一门新语言,不仅要多读文档,还要多看别人写的代码,更要强迫自己用新语言多写代码。我在学习 Golang 之前,读过好几本相关的书籍,不过总感觉没真正学会,于是我决定动手用 Golang 写一个能用的工具试试,因为 Golang 最大的优势就是 goroutine 和 channel,所以我觉得实现一个简版的 ab(Web 压力测试工具)应该是一个不错的选择,用 Golang 磕磕绊绊总算实现了预想的功能,能够计算 Requests per second 和 Time per request 的值,不过总感觉写出来的代码不够漂亮,于是我又找来 hey 的代码前后读了几遍,然后结合自己的理解临摹了一遍,感觉总算是入门了。

虽然 hey 的代码本身已经相当简洁,但是洋洋洒洒加起来也有五六百行代码,下面是我默写的版本,仅保留主体功能,总共就一两百行代码:

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "os"
    "strings"
    "sync"
    "time"
)

var usage = `Usage: %s [options]
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -s timeout      Seconds to max. wait for each response
    -m method       Method name
`

var (
    requests    int
    concurrency int
    timeout     int
    method      string
    url         string
)

func main() {
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, usage, os.Args[0])
    }

    flag.IntVar(&requests, "n", 1000, "")
    flag.IntVar(&concurrency, "c", 100, "")
    flag.IntVar(&timeout, "s", 10, "")
    flag.StringVar(&method, "m", "GET", "")
    flag.Parse()

    if flag.NArg() != 1 {
        exit("Invalid url.")
    }

    method = strings.ToUpper(method)
    url = flag.Args()[0]

    if method != "GET" {
        exit("Invalid method.")
    }

    if requests < 1 || concurrency < 1 {
        exit("-n and -c cannot be smaller than 1.")
    }

    if requests < concurrency {
        exit("-n cannot be less than -c.")
    }

    w := Work{
        Requests:    requests,
        Concurrency: concurrency,
        Timeout:     timeout,
        Method:      method,
        Url:         url,
    }

    w.Run()
}

func exit(msg string) {
    flag.Usage()
    fmt.Fprintln(os.Stderr, "\n[Error] "+msg)
    os.Exit(1)
}

type Work struct {
    Requests    int
    Concurrency int
    Timeout     int
    Method      string
    Url         string
    results     chan *Result
    start       time.Time
    end         time.Time
}

type Result struct {
    Duration time.Duration
}

func (w *Work) Run() {
    w.results = make(chan *Result, w.Requests)
    w.start = time.Now()
    w.runWorkers()
    w.end = time.Now()

    w.print()
}

func (w *Work) runWorkers() {
    var wg sync.WaitGroup

    wg.Add(w.Concurrency)

    for i := 0; i < w.Concurrency; i++ {
        go func() {
            defer wg.Done()
            w.runWorker(w.Requests / w.Concurrency)
        }()
    }

    wg.Wait()
    close(w.results)
}

func (w *Work) runWorker(num int) {
    client := &http.Client{
        Timeout: time.Duration(w.Timeout) * time.Second,
    }

    for i := 0; i < num; i++ {
        w.sendRequest(client)
    }
}

func (w *Work) sendRequest(client *http.Client) {
    req, err := http.NewRequest(w.Method, w.Url, nil)

    if err != nil {
        log.Fatal(err.Error())
    }

    start := time.Now()
    client.Do(req)
    end := time.Now()

    w.results <- &Result{
        Duration: end.Sub(start),
    }
}

func (w *Work) print() {
    sum := 0.0
    num := float64(len(w.results))

    for result := range w.results {
        sum += result.Duration.Seconds()
    }

    rps := int(num / w.end.Sub(w.start).Seconds())
    tpr := sum / num * 1000

    fmt.Printf("Requests per second:\t%d [#/sec]\n", rps)
    fmt.Printf("Time per request:\t%.3f [ms]\n", tpr)
}

代码虽短,却涵盖了 Golang 常见的用法,如果你想学习 Golang,不妨亲自动手实现一下本例子,搞懂它基本就可以算是入门了。

原文链接: https://huoding.com/2017/06/09/623

暂无回复。
需要 登录 后方可回复。