一、前言
公司后端服务已经全部微服务化,想要调试某个服务可以使用 grpcui,但要对某个接口进行压测,grpcui 还做不到。诸多努力之后找到本次主角:https://github.com/bojand/ghz,官网:ghz.sh。
推荐理由:简洁!可以一次性解决掉 proto 文件相互之间引用的烦心事!
二、使用
这里只介绍在
Mac环境下的用法,其他环境请参阅官网。另:我们仍旧使用
GOPATH方式来管理包,我的:export GOPATH=/Users/hujiaming/go,本次测试目录为:/Users/hujiaming/go/src/hujm.net。
1. 安装
直接使用 brew 来安装:
brew install ghz
如果不成功,可以直接去 https://github.com/bojand/ghz/releases 下载二进制,下载后放在 PATH 中即可。
注:还需要有 protoc 工具。
2. 生成 protoset 文件
如果你的 proto 文件中还引用了其他文件,强烈建议使用 protoset 方式。
假如我在如下的 proto 中定义一个 GRPC服务:
/**
* @filename: api.proto
*/
syntax = "proto3";
import "github.com/mwitkow/go-proto-validators/validator.proto";
import "xxx/mms2/utils/i18n/moneypb/money.proto";
import "xxx/cm/fulfillment/offermanager/offerpb/offer.proto";
import "xxx/cm/fulfillment/offermanager/offerpb/association.proto";
import "xxx/cm/fulfillment/offermanager/offerpb/supplier.proto";
import "xxx/cm/core/price/pricepb/price.proto";
package offerpb;
service ApiService {
rpc CreateSKUAssociations(CreateSKUAssociationsReq) returns (CreateSKUAssociationsReply) {};
}
message CreateSKUAssociationsReq {
repeated Association associations = 1 [ (validator.field) = {repeated_count_min : 1} ];
}
message CreateSKUAssociationsReply {}
而 Association 是定义在 "xxx/cm/fulfillment/offermanager/offerpb/association.proto” 文件中的:
/**
* @filename: association.proto
*/
syntax = "proto3";
import "github.com/mwitkow/go-proto-validators/validator.proto";
import "xxx/mms2/utils/i18n/moneypb/money.proto";
import "xxx/cm/fulfillment/offermanager/offerpb/supplier.proto";
import "xxx/cm/core/price/pricepb/price.proto";
package offerpb;
message Association {
int64 offer_id = 1 [ (validator.field) = {int_gt : 1000000000} ];
string sku_code = 2 [ (validator.field) = {string_not_empty : true} ];
string author = 3 [ (validator.field) = {string_not_empty : true} ];
}
如果采用非 protoset 方式,可能要先生成 association.pb.go,再生成 api.pb.go 文件。这里我们采用 protoset 方式,一步到位:
protoc \
--include_imports \
-I. -I/usr/local/include \
-I/usr/local/go \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
-I$GOPATH/src \
--proto_path=. \
--descriptor_set_out=api.bundle.protoset \
api.proto
需要注意的点如下:
- 如果你的
proto文件中有引用,上述命令中一定要有include_imports参数; - 在后面运行时如果出现
no descriptor found for "xxxxxx",可能是某个文件没有通过-I引用进来,记得加上重新执行。
不出意外,会在当前目录下生成 api.bundle.protoset 文件。
3. 执行压测任务
可以看一下 ghz 的用法:
$ ghz --help
usage: ghz [<flags>] [<host>]
Flags:
-h, --help Show context-sensitive help (also try --help-long and --help-man).
--config= Path to the JSON or TOML config file that specifies all the test run settings.
--proto= The Protocol Buffer .proto file.
--protoset= The compiled protoset file. Alternative to proto. -proto takes precedence.
--call= A fully-qualified method name in 'package.Service/method' or 'package.Service.Method' format.
-i, --import-paths= Comma separated list of proto import paths. The current working directory and the directory of the protocol buffer file are automatically added to the import list.
--cacert= File containing trusted root certificates for verifying the server.
--cert= File containing client certificate (public key), to present to the server. Must also provide -key option.
--key= File containing client private key, to present to the server. Must also provide -cert option.
--cname= Server name override when validating TLS certificate - useful for self signed certs.
--skipTLS Skip TLS client verification of the server's certificate chain and host name.
--skipFirst=0 Skip the first X requests when doing the results tally.
--insecure Use plaintext and insecure connection.
--authority= Value to be used as the :authority pseudo-header. Only works if -insecure is used.
-c, --concurrency=50 Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50.
-n, --total=200 Number of requests to run. Default is 200.
-q, --qps=0 Rate limit, in queries per second (QPS). Default is no rate limit.
-t, --timeout=20s Timeout for each request. Default is 20s, use 0 for infinite.
-z, --duration=0 Duration of application to send requests. When duration is reached, application stops and exits. If duration is specified, n is ignored. Examples: -z 10s -z 3m.
-x, --max-duration=0 Maximum duration of application to send requests with n setting respected. If duration is reached before n requests are completed, application stops and exits. Examples: -x
10s -x 3m.
--duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore.
-d, --data= The call data as stringified JSON. If the value is '@' then the request contents are read from stdin.
-D, --data-file= File path for call data JSON file. Examples: /home/user/file.json or ./file.json.
-b, --binary The call data comes as serialized binary message or multiple count-prefixed messages read from stdin.
-B, --binary-file= File path for the call data as serialized binary message or multiple count-prefixed messages.
-m, --metadata= Request metadata as stringified JSON.
-M, --metadata-file= File path for call metadata JSON file. Examples: /home/user/metadata.json or ./metadata.json.
--stream-interval=0 Interval for stream requests between message sends.
--reflect-metadata= Reflect metadata as stringified JSON used only for reflection request.
-o, --output= Output path. If none provided stdout is used.
-O, --format= Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary.
--connections=1 Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1.
--connect-timeout=10s Connection timeout for the initial connection dial. Default is 10s.
--keepalive=0 Keepalive time duration. Only used if present and above 0.
--name= User specified name for the test.
--tags= JSON representation of user-defined string tags.
--cpus=8 Number of cpu cores to use.
--debug= The path to debug log file.
-e, --enable-compression Enable Gzip compression on requests.
-v, --version Show application version.
Args:
[<host>] Host and port to test.
需要关注的几个参数:
--skipTLS --insecure:如果服务不支持HTTPS的话,可以使用此参数跳过TLS验证;--protoset:指定本次运行的protoset文件路径,即上面生成的api.bundle.protoset;--call:需要调用的方法名,格式为:包名.服务名.方法名。比如我要调用offerpb包下的ApiService服务的CreateSKUAssociations方法,那么call参数应该是:--call offerpb.ApiService.CreateSKUAssociations;--data:本次请求的参数,通过jsonString的格式传入;--data-file:本次请求的参数,只不过通过文件的形式传入,文件中是标准的通过json序列化后的数据;--metadata:metadata参数,通过jsonString的格式传入;-c:并发数,默认 50(这里有坑,具体参照官网解释:-c。虽然会其多个goroutine,但是所有的goroutine会公用一个连接);-n:请求数,默认 200。n不能小于c。
假设 ApiService服务的地址是:localhost:58784。我们执行下面的命令,发起一次压测任务:
$ ghz \
--skipTLS --insecure --protoset /Users/hujiaming/go/src/hujm.net/api.bundle.protoset \
--call offerpb.ApiService.CreateSKUAssociations \
--data '{"associations":[{"sku_code": "test:6985079117562211244","offer_id": 8629237865019910744,"author": "test_by_ghz"}]}' \
-m '{"name": "test"}' \
-c 100 -n 1000 \
localhost:58784
当你的请求参数比较多时,将他们放在一个文件中、然后使用 --data-file 参数是更好的选择:
$ cat test_data.json
{
"associations": [
{
"sku_code": "test:6237052533738512496",
"offer_id": 5655307241153104444,
"author": "test_by_ghz"
},
{
"sku_code": "test:2156276639623439583",
"offer_id": 6360134836979240095,
"author": "test_by_ghz"
},
{
"sku_code": "test:8361104385030719827",
"offer_id": 3705044490439993926,
"author": "test_by_ghz"
},
{
"sku_code": "test:6023087259299523902",
"offer_id": 3776027093787512475,
"author": "test_by_ghz"
},
{
"sku_code": "test:9196748606623463644",
"offer_id": 1506864634761125694,
"author": "test_by_ghz"
}
]
}
$ ghz \
--skipTLS --insecure --protoset /Users/hujiaming/go/src/hujm.net/api.bundle.protoset \
--call offerpb.ApiService.CreateSKUAssociations \
--data-file /Users/hujiaming/go/src/hujm.net/test_data.json \
-m '{"name": "test"}' \
-c 100 -n 1000 \
localhost:58784
看下输出:
Summary:
Count: 1000
Total: 743.17 ms
Slowest: 194.74 ms
Fastest: 37.67 ms
Average: 69.32 ms
Requests/sec: 1345.59
Response time histogram:
37.670 [1] |
53.377 [384] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
69.084 [349] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
84.791 [138] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎
100.498 [26] |∎∎∎
116.205 [2] |
131.912 [0] |
147.619 [16] |∎∎
163.326 [33] |∎∎∎
179.033 [17] |∎∎
194.739 [34] |∎∎∎∎
Latency distribution:
10 % in 46.59 ms
25 % in 49.94 ms
50 % in 57.28 ms
75 % in 69.51 ms
90 % in 102.33 ms
95 % in 163.38 ms
99 % in 183.99 ms
Status code distribution:
[OK] 1000 responses
Summary 的参数:
Count:完成的请求总数,包括成功的和失败的;Total:本次请求所用的总时长,从ghz启动一直到结束;Slowest:最慢的某次请求的时间;Fastest:最快的某个请求的时间;Average:(所有请求的响应时间) / Count。Requests/sec:RTS,Count / Total的值。