本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本文主要介绍了 gRPC 压测工具 ghz ,包括 ghz 的安装、使用及压测计划制定等。
- 安装可以直接在Release页面下载二进制文件,也可以 clone 仓库手动编译。
下载解压后即可使用
# 下载
$ wget https://github.91chifun.workers.dev/https://github.com//bojand/ghz/releases/download/v0.94.0/ghz-linux-x86_64.tar.gz
ghz-linux-x86_64.ta 100%[===================>] 10.41M 1.84MB/s 用时 5.7s
# 解压
$ tar -zxvf ghz-linux-x86_64.tar.gz
ghz
ghz-web
LICENSE
$ ls
ghz ghz-linux-x86_64.tar.gz ghz-web LICENSE
# 添加到环境变量
$ sudo vim /etc/profile
$ source /etc/profile
# 具体位置就是刚解压的位置
$ cat /etc/profile
export PATH=$PATH:/home/xiedeju/ghz
具体语法
ghz [<flags>] [<host>]
2. 参数说明
只列出了常用参数,其他参数可以查看官方文档或者查阅帮助命令ghz -h
大致可以分为三类参数:
2.1 基本参数
- --config:指定配置文件位置
- --proto:指定 proto 文件位置
- 会从 proto 文件中获取相关信息
- --call:指定调用的方法。
- 具体格式为包名.服务名.方法名
- 如:--call helloworld.Greeter.SayHello
- -c:并发请求数
- -n:最大请求数,达到后则结束测试
- -d:请求参数
- JSON格式,如-d '{"name":"Bob"}'
- -D:以文件方式指定请求参数,JSON文件位置
- 如-D ./file.json
- -o:输出路径
- 默认输出到 stdout
- -O/--format:输出格式,有多种格式可选
- 便于查看的:csv、json、pretty、html:
- 便于入库的:influx-summary、influx-details:满足InfluxDB line-protocol 格式的输出
- 以上就是相关的基本参数,有了这些参数基本可以进行测试了。
2.2 负载参数
负载参数主要控制ghz每秒发起的请求数(RPS)。
- -r/--rps:指定RPS
- ghz以恒定的RPS进行测试
- --load-schedule:负载调度算法,取值如下:
- const:恒定RPS,也是默认调用算法
- step:步进增长RPS,需要配合load-start,load-step,load-end,load-step-duration,和load-max-duration等参数
- line:线性增长RPS,需要配合load-start,load-step,load-end,和load-max-duration等参数,其实line就是 step 算法将load-step-duration时间固定为一秒了。
- --load-start:step、line 的起始RPS
- --load-step:step、line 的步进值或斜率值
- --load-end:step、line 的负载结束值
- --load-max-duration:最大持续时间,到达则结束
例如
-n 10000 -c 10 --load-schedule=step --load-start=50 --load-step=10 --load-step-duration=5s
从50RPS开始,每5秒钟增加10RPS,一直到完成10000请求为止。
-n 10000 -c 10 --load-schedule=step --load-start=50 --load-end=150 --load-step=10 --load-step-duration=5s
从50RPS开始,每5秒钟增加10RPS,最多增加到150RPS,一直到完成10000请求为止。
-n 10000 -c 10 --load-schedule=line --load-start=200 --load-step=-2 --load-end=50
从200RPS开始,每1秒钟降低2RPS,一直降低到50RPS,一直到完成10000请求为止。
line 其实就是 step,只不过是把–load-step-duration固定为1秒了
2.3 并发参数
- -c:并发woker数,
- 注意:不是并发请求数
- --concurrency-schedule:并发调度算法,和--load-schedule类似
- const:恒定并发数,默认值
- step:步进增加并发数
- line:线性增加并发数
- --concurrency-start:起始并发数
- --concurrency-end:结束并发数
- --concurrency-step:并发数步进值
- --concurrency-step-duration:在每个梯段需要持续的时间
- --concurrency-max-duration:最大持续时间
例子:
-n 100000 --rps 200 --concurrency-schedule=step --concurrency-start=5 --concurrency-step=5 --concurrency-end=50 --concurrency-step-duration=5s
固定RPS200,worker数从5开始,每5秒增加5,最大增加到50。
注意:5个worker时也要完成200RPS,即每个worker需要完成40RPS,到50个worker时只需要每个worker完成4RPS即可达到200RPS。
2.4 配置文件
所有参数都可以通过配置文件来指定,这也是比较推荐的用法。
比如这样:
{
"proto": "/path/to/greeter.proto",
"call": "helloworld.Greeter.SayHello",
"total": 2000,
"concurrency": 50,
"data": {
"name": "Joe"
},
"metadata": {
"foo": "bar",
"trace_id": "{{.RequestNumber}}",
"timestamp": "{{.TimestampUnix}}"
},
"import-paths": [
"/path/to/protos"
],
"max-duration": "10s",
"host": "0.0.0.0:50051"
}
3. 使用
该工具有两种使用方式。
- 1)ghz 二进制文件方式,通过命令行参数或者配置文件指定配置信息
- 2)ghz/runner编程方式使用,通过代码指定配置信息
二者只是打开方式不同,具体原理是一样的。
首页启动服务端,这里就是要之前HelloWorld教程中的Greeter服务。
root@17x:~/17x/projects/grpc-go-example/helloworld/server$ go run main.go
2021/04/17 10:53:46 Serving gRPC on 0.0.0.0:50051
3.1 命令行方式
1)基本参数
首先使用基本参数进行测试
需要在Proto所在的目录进行
ghz -c 10 -n 1000 \
--insecure \
--proto ./hello_world.proto \
--call helloworld.Greeter.SayHello \
-d '{"name":"Joe"}' \
0.0.0.0:50051
--call helloworld.Greeter.SayHello:说明,具体 proto 文件如下
// 省略其他代码...
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
可以看到,包名为helloworld、 service名为Greeter,方法名为 SayHello。
结果如下
root@xiedeju:/home/xiedeju/Code/rpcServer/src/commmethod/resource# ghz --insecure --async --proto /protos/helloworld.proto \
> --call helloworld.HelloworldService.SayHello \
> -n 10000 --rps 200 \
> --concurrency-schedule=step --concurrency-start=5 --concurrency-step=5 --concurrency-end=50 --concurrency-step-duration=5s \
> -d '{"name":"{{.WorkerID}}"}' 192.168.148.34:50051
Summary:
Count: 10000
Total: 50.04 s
Slowest: 41.92 ms
Fastest: 0.21 ms
Average: 1.95 ms
Requests/sec: 199.83
Response time histogram:
0.212 [1] |
4.383 [7018] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
8.554 [2961] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
12.725 [16] |
16.896 [3] |
21.068 [0] |
25.239 [0] |
29.410 [0] |
33.581 [0] |
37.752 [0] |
41.923 [1] |
Latency distribution:
10 % in 0.36 ms
25 % in 0.42 ms
50 % in 0.50 ms
75 % in 4.86 ms
90 % in 5.54 ms
95 % in 5.91 ms
99 % in 6.22 ms
Status code distribution:
[OK] 10000 responses
大部分请求都能在3ms左右响应。
2)负载参数
接着增加负载参数
ghz -c 10 -n 1000 \
--insecure \
--proto ./hello_world.proto \
--call helloworld.Greeter.SayHello \
-d '{"name":"Joe"}' \
--load-schedule=step --load-start=50 --load-step=10 --load-step-duration=5s \
-o report.html -O html \
0.0.0.0:50051
这次指定使用HTML方式输出结果,执行完成后可以在当前目录看到输出的HTML文件
$ ls
report.html

相比之下HTML方式更加直观。
3)并发参数
最后使用并发参数
ghz -c 10 -n 1000 \
--insecure \
--proto ./helloworld.proto \
--call helloworld.HelloworldService.SayHello \
-d '{"name":"Joe"}' \
--load-schedule=step --load-start=50 --load-step=10 --load-step-duration=5s \
-o report.json -O pretty \
192.168.148.34:50051
本次以CSV格式打印输出
"date": "2021-06-11T16:47:21+08:00",
"endReason": "normal",
"options": {
"call": "helloworld.HelloworldService.SayHello",
"host": "192.168.148.34:50051",
"proto": "./helloworld.proto",
"import-paths": [
"."
],
"insecure": true,
"load-schedule": "step",
"load-start": 50,
"load-end": 0,
"load-step": 10,
"load-step-duration": 5000000000,
"load-max-duration": 0,
"concurrency": 10,
"concurrency-schedule": "const",
"concurrency-start": 0,
"concurrency-end": 0,
"concurrency-step": 1,
"concurrency-step-duration": 0,
"concurrency-max-duration": 0,
"total": 1000,
"connections": 1,
"timeout": 20000000000,
"dial-timeout": 10000000000,
"data": {
"name": "Joe"
},
"binary": false,
"CPUs": 1
},
"count": 1000,
"total": 16250590664,
"average": 487617,
"fastest": 340844,
"slowest": 24022390,
"rps": 61.536224785681426,
"errorDistribution": {},
"statusCodeDistribution": {
"OK": 1000
},
CSV和JSON格式会将每次请求及其消耗时间、状态等信息一一列出,信息比较全,不过相比HTML不够直观。
3.2 ghz/runner编程方式
编程方式更加灵活,同时可以直接使用二进制请求数据也比较方便。
完整代码见 Github
相关代码如下:
package main
import (
"log"
"os"
"github.com/bojand/ghz/printer"
"github.com/bojand/ghz/runner"
"github.com/golang/protobuf/proto"
pb "github.com/lixd/grpc-go-example/helloworld/helloworld"
)
// 官方文档 https://ghz.sh/docs/intro.html
func main() {
// 组装BinaryData
item := pb.HelloRequest{Name: "lixd"}
buf := proto.Buffer{}
err := buf.EncodeMessage(&item)
if err != nil {
log.Fatal(err)
return
}
report, err := runner.Run(
// 基本配置 call host proto文件 data
"helloworld.Greeter.SayHello", // 'package.Service/method' or 'package.Service.Method'
"localhost:50051",
runner.WithProtoFile("../helloworld/helloworld/hello_world.proto", []string{}),
runner.WithBinaryData(buf.Bytes()),
runner.WithInsecure(true),
runner.WithTotalRequests(10000),
// 并发参数
runner.WithConcurrencySchedule(runner.ScheduleLine),
runner.WithConcurrencyStep(10),
runner.WithConcurrencyStart(5),
runner.WithConcurrencyEnd(100),
)
if err != nil {
log.Fatal(err)
return
}
// 指定输出路径
file, err := os.Create("report.html")
if err != nil {
log.Fatal(err)
return
}
rp := printer.ReportPrinter{
Out: file,
Report: report,
}
// 指定输出格式
_ = rp.Print("html")
}
运行测试会在当前目录输出report.html文件
$ go run ghz.go
$ ls
ghz.go report.html
4. 小结
推荐使用ghz/runner编程方式+HTML格式输出结果。
- ghz/runner编程方式相比二进制方式更加灵活
- HTML格式输出结果更加直观