Week7 命题作业(性能压测工具编写)
发布于: 2020 年 07 月 19 日
1、性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?
随着并发压力的增加
系统响应时间先是基本不变,然后再缓慢增加,最后急剧增加至无限大。
吞吐量先是随着并发压力(数)的增加而线性增加,而后增速缓慢,最后逐渐下降再急剧下降在某点变为0。
这是因为在并发压力从0开始时,系统资源是足够的,对每个请求的处理不会阻塞,所以一开始系统响应时间基本不变,而且吞吐量=并发数/响应时间(s),在响应时间基本不变的前提下,吞吐量与并发数成正比,自然是线性增长的。
随着并发压力逐渐增大至超过资源刚好够用的一个点,继续增加并发压力,新的请求进入计算机没有足够的资源进行处理,就会去排队,就产生了阻塞,响应时间会随着并发数的增加而缓慢增加,吞吐量=并发数/响应时间(s),分母缓慢增大,吞吐量增速变慢。
并发压力继续增大,超过了系统的最大负载点,开始出现响应错误或者超时(队列可能已满或者排队时间过长),响应时间急剧增加,响应错误率也急剧增加,由公式吞吐量=并发数/响应时间(s),吞吐量急剧降低,到达系统崩溃点时系统已无法处理新的请求,响应时间无限大(即吞吐量极限为0,有限的并发数除以无限大的响应时间可得),整个系统失去响应,系统崩溃。
2、用你熟悉的编程语言写一个Web性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95%响应时间。用这个测试工具以10并发、100次请求压测www.baidu.com。
运行结果如下:
-n
后接请求总次数,-c
后接并发数,然后输入URL
代码基于https://github.com/kaimixu/kbang 进行修改,详细代码如下:
main.go
↓
package mainimport ( "flag" "fmt" "os" "runtime" "PerformancePressureTest/robot")var ( n, c, t int url string)var usage = `Usage: main [options...] <url> options: -n Number of requests to run (default: 10) -c Number of requests to run concurrency (default: 1) -t Request connection timeout in second (default: 5s)`func main() { runtime.GOMAXPROCS(runtime.NumCPU()) flag.Usage = func() { fmt.Fprint(os.Stderr, usage) } flag.IntVar(&n, "n", 10, "") flag.IntVar(&c, "c", 1, "") flag.IntVar(&t, "t", 5, "") flag.Parse() if flag.NArg() < 1 { abort("") } var httpConf = robot.HttpConf{ Timeout: t, } if flag.NArg() > 0 { url = flag.Args()[0] } else { url = "https://www.taobao.com/" } httpConf.Request = robot.RequestConf{ Method: "GET", Url: url, } robot := robot.NewRoboter(n, c, &httpConf) err := robot.CreateRequest() if err != nil { abort(err.Error()) } robot.Run()}func abort(errmsg string) { if errmsg != "" { fmt.Fprintf(os.Stderr, "%s\n\n", errmsg) } flag.Usage() os.Exit(1)}
robot/robot.go
↓
package robotimport ( "errors" "fmt" "net" "net/http" "os" "os/signal" "strings" "sync" "time")type RequestConf struct { Method string `method` Url string `url`}type HttpConf struct { Timeout int `timeout` Request RequestConf `request`}type Roboter struct { n int c int count int httpConf *HttpConf hc *http.Client requests []*http.Request output *output}func NewRoboter(n, c int, httpConf *HttpConf) *Roboter { return &Roboter{ n: n, c: c, httpConf: httpConf, requests: make([]*http.Request, 0), output: newOutput(n, c), }}func (this *Roboter) CreateRequest() error { type request struct { preq *http.Request conf *RequestConf } reqC := this.httpConf.Request method := strings.ToUpper(reqC.Method) if method != "GET" { return errors.New("invalid http method") } r, err := http.NewRequest(method, reqC.Url, nil) if err != nil { return err } for i := 0; i < this.n; i++ { req := &request{preq: r, conf: &reqC} this.requests = append(this.requests, cloneRequest(req.preq)) } return nil}func (this *Roboter) Run() { s := make(chan os.Signal, 1) signal.Notify(s, os.Interrupt) tr := &http.Transport{ Dial: (&net.Dialer{ Timeout: time.Duration(this.httpConf.Timeout) * time.Second, KeepAlive: 30 * time.Second, }).Dial, DisableKeepAlives: true, } this.hc = &http.Client{Transport: tr} fmt.Println("start...") st := time.Now() go func() { <-s fmt.Println("receive sigint") this.output.finalize(time.Now().Sub(st).Seconds()) os.Exit(1) }() this.startWorkers() this.output.finalize(time.Now().Sub(st).Seconds())}func (this *Roboter) startWorkers() { var wg sync.WaitGroup wg.Add(this.c) for i := 0; i < this.c; i++ { go func(rid int) { this.startWorker(rid, this.n/this.c) wg.Done() }(i) } wg.Wait()}func (this *Roboter) startWorker(rid, num int) { for i := 0; i < num; i++ { req := this.requests[rid*num+i] this.sendRequest(req) }}func (this *Roboter) sendRequest(req *http.Request) { s := time.Now() var code int resp, err := this.hc.Do(req) if err == nil { code = resp.StatusCode } this.output.addResult(&result{ statusCode: code, duration: time.Now().Sub(s), })}func cloneRequest(r *http.Request) *http.Request { r2 := new(http.Request) *r2 = *r r2.Header = make(http.Header, len(r.Header)) for k, s := range r.Header { r2.Header[k] = append([]string(nil), s...) } return r2}
robot/print.go
↓
package robotimport ( "fmt" "os" "sort" "time")type result struct { statusCode int duration time.Duration}type output struct { average float64 percent95 float64 rps float64 n int c int reqNumTotal int64 reqNumFail int64 reqNumSucc int64 costTimeTotal float64 results chan *result}var durationSort []float64func newOutput(n int, c int) *output { return &output{ n: n, c: c, results: make(chan *result, n), }}func (this *output) addResult(res *result) { this.results <- res}func (this *output) finalize(costTime float64) { this.costTimeTotal = costTime for { select { case res := <-this.results: this.reqNumTotal++ durationSort = append(durationSort, res.duration.Seconds()) if res.statusCode != 200 { this.reqNumFail++ } else { this.reqNumSucc++ } default: sort.Float64s(durationSort) index95 := this.reqNumTotal * 95 / 100 this.percent95 = durationSort[index95] this.rps = float64(this.reqNumTotal) / this.costTimeTotal this.average = this.costTimeTotal / float64(this.reqNumTotal) this.print() return } }}func (this *output) print() { if this.reqNumTotal > 0 { fmt.Printf("总结:\n") fmt.Printf(" 并发数:\t%d\n", this.c) fmt.Printf(" 总花费时间:\t%0.4f 秒\n", this.costTimeTotal) fmt.Printf(" 完成请求数:\t%d\n", this.reqNumTotal) fmt.Printf(" 失败请求数:\t%d\n", this.reqNumFail) fmt.Printf(" 成功请求数:\t%d\n", this.reqNumSucc) fmt.Printf(" 每秒请求数:\t%0.4f\n", this.rps) fmt.Printf(" 平均响应时间:\t%0.4f 秒\n", this.average) fmt.Printf(" 95%%响应时间:\t%0.4f 秒\n", this.percent95) } os.Exit(0)}
划线
评论
复制
发布于: 2020 年 07 月 19 日 阅读数: 64
版权声明: 本文为 InfoQ 作者【星河寒水】的原创文章。
原文链接:【http://xie.infoq.cn/article/dd5b91afaa6c022a163d594b3】。未经作者许可,禁止转载。
星河寒水
关注
还未添加个人签名 2018.09.17 加入
还未添加个人简介
评论