整体概述
由于工作的原因,平时需要对服务的gRPC接口进行测试。之前一直使用JMeter编写Java请求的方式来测试的,但一直不清楚这种方式有什么局限。最近有时间看一些其他常用的性能测试工具(包括K6、locust),但发现网上的资料大多是http请求相关的。于是决定针对gRPC的请求进行一组简单的测试,计划从:脚本的维护难度、工具的资源占用、能支撑的并发数量和结果输出的完整性等方面进行比较。
为避免过多的资源消耗,保持执行方式的统一,最终都选用无UI的命令行方式。其他基础信息如下:
- 工具信息
- 环境信息
使用两台 CentOS Linux release 7.9 的服务器进行测试,一台压力机一台服务机。
- 服务信息
使用一个Java API服务作为测试对象,接口使用gRPC协议,请求方式为POST,接口无状态,服务处理完成后直接返回结果。
测试内容
关于安装
- JMeter
下载压缩包Apache JMeter,在Java环境运行即可。
- JMeter grpc plugin:需要额外下载插件jmeter-grpc-plugin,下载之后放到lib/ext中即可。
- JMeter Java request:需要将开发完成的jar放到lib/ext。
- K6
执行命令安装Grafana k6 documentation,安装后即可通过命令运行。
sudo yum install https://dl.k6.io/rpm/repo.rpm sudo yum install k6
- Locust
依赖Python环境,执行命令安装Locust
pip install locust
要执行gRPC请求,需要额外的依赖完成proto的解析gRPC Python
python -m pip install grpcio python -m pip install grpcio-tools
注意:Linux环境执行时,可能还需要安装protobuf。
关于脚本
- JMeter grpc plugin
纯GUI的方式编写,可以说非常简单了,只需要按照接口规则传参即可,上手难度很低。
注意:把proto依赖路径填好,Listing就能选择请求方式了
- JMeter Java request
需要先编写Java调用代码,然后打成Jar,放到JMeter的依赖路径里。Java核心处理代码如下:
public SampleResult runTest(JavaSamplerContext javaSamplerContext) { SampleResult sampleResult = new SampleResult(); try { this.setupValues(javaSamplerContext); byte[] fileContent = fileList.get(); ImageQualityDetectRequest imageQualityDetectRequest = ImageQualityDetectRequest.newBuilder() .setRequester(Requester.newBuilder().setRequestSequence(RequestSequence).build()) .setImage(Image.newBuilder() .setImageBytes(UnsafeByteOperations.unsafeWrap(fileContent)).build()) .build(); sampleResult.sampleStart(); ImageQualityDetectReply replies = stub.imageQualityDetect(imageQualityDetectRequest); sampleResult.setSuccessful(false); if (replies != null) { sampleResult.setSuccessful(true); sampleResult.setResponseCodeOK(); sampleResult.setSentBytes(fileContent.length); sampleResult.setRequestHeaders(path + "\nindex:" + index); sampleResult.setResponseHeaders(replies.getApiCallInfo().toString()); sampleResult.setResponseData(String.valueOf(replies), "UTF-8"); } } catch (Exception e) { sampleResult.setSuccessful(false); sampleResult.setResponseMessage(String.valueOf(e)); e.printStackTrace(); } finally { sampleResult.sampleEnd(); } return sampleResult; }
之后就是在GUI中添加Java请求,选择需要调用的类方法,维护接口参数就可以了。
- K6
参考官方文档中的规则编写测试脚本,完成后使用命令运行脚本即可。主要测试代码如下:
export default function () { client.connect('192.168.0.30:8000', { plaintext: true, timeout: '30s', }); const requestData = { image: { image_bytes: base64Data } }; const response = client.invoke('quality.QualityService/Quality', requestData); check(response, { 'status is OK': (r) => r && r.status === StatusOK, 'status code is 2000': (r) => r.message?.response?.status === '2000', 'result is true': (r) => r.message?.result === true, 'score is over 80': (r) => r.message?.score > 80, }); client.close(); }
- Locust
Locust进行gRPC协议的请求,是建立在Python对gRPC支持的基础上的,因此需要先将proto文件解析为py文件
python -m grpc_tools.protoc --proto_path=proto --python_out=proto --grpc_python_out=proto proto/quality_detection.proto proto/commons.proto
测试运行脚本中加入gRPC调用的部分即可,测试脚本核心代码如下:
@task def image_quality_detect(self): start_time = time.time() try: request = QualityRequest() request.image.image_bytes = image_data response = self.stub.QualityDetect(request) response_time = int((time.time() - start_time) * 1000) if response.response.status == '2000' and response.result == True and response.score > 80: events.request.fire( request_type="gRPC", name="QualityDetect", response_time=response_time, response_length=0, exception=None, context={} ) else: events.request.fire( request_type="gRPC", name="QualityDetect", response_time=response_time, response_length=0, exception=Exception(f"Invalid response: status={response.response.status}, result={response.result}, score={response.score}"), context={} ) except Exception as e: response_time = int((time.time() - start_time) * 1000) events.request.fire( request_type="gRPC", name="QualityDetect", response_time=response_time, response_length=0, exception=e, context={} ) raise
请求能力
测试过程中依次增加10并发至60并发,每次运行5分钟,收集结果如下:
比较发现JMeter Java请求方式和K6的方式得到的TPS基本相同,只有0.72%的差距。不过K6的方式在低并发时TPS会比JMeter Java请求的方式要少一些,并发增大后,差距基本就没有了。使用JMeter grpc plugin的方式的话就会有24.65%的较大差距。而使用Locust的话,可能当前Python调用gRPC方式并没有实现真正的并发,暂时没有进行针对性的研究(如果有大神指点一下的话非常感谢)。
响应时间的话选取的平均响应时间做比较,由于JMeter并没有收集测试执行文件,因此没有90%和95%这类数据,而K6的命令行执行结果自带这两项统计,算是不错的优势。Locust虽然有统计结果,但是因为QPS是瞬时大小。
服务资源使用
当前服务主要性能瓶颈在CPU的使用上,这里以服务器CPU使用率来进行比较。
- JMeter grpc plugin
- JMeter Java request
- K6
- Locust
通过以上几张图可以发现,使用JMeter Java request的方式可以使服务器的CPU持续运行在高位,相对波动最少。其次是K6的方式,服务CPU使用也很稳定,但在低并发时,CPU使用率会比JMeter Java request的方式略低,并且会有少量较大的使用率波动。使用JMeter grpc plugin的方式也能使服务器CPU在较高水平运行,但是有持续的波动,不会稳定维持在高位。Locust明显没有给服务增加并发压力,一直保持在单线程的水准。
资源消耗
对于工具的资源使用情况,没有监控具体的进程,也是以压力机整个环境的使用情况为记录,粗略的比较CPU使用率和内存使用情况,如下:
- JMeter grpc plugin
- JMeter Java request
- K6
- Locust
通过以上机组图可以发现,使用JMeter grpc plugin和JMeter Java request的方式都会在测试期间使用额外内存来处理gRPC请求,并在执行结束时回收内存。但是JMeter Java request使用的内存会比JMeter grpc plugin小。并且JMeter grpc plugin的CPU使用率会随着请求的增多而增多,而JMeter Java request可以通过代码来复用gRPC连接,仅在测试初期创建对象时会有较高的CPU使用,随后就维持在一个较低的水平。
K6的方式和JMeter grpc plugin的方式相似,也是请求增多CPU使用率越高(应该是每次请求都重新创建gRPC连接)。但是由于K6天生对gRPC良好的支持,工具内存使用明显低很多。
Locust因为并没有很大的并发和请求数,此处忽略。
一些问题
- JMeter grpc plugin
当开始有并发时,控制台和jmeter.log日志中会出现错误(见下图),进而导致控制台无法输出执行结果。
protoc-jar: embedded: bin/3.21.4/protoc-3.21.4-linux-x86_64.exe
protoc-jar: executing: [/tmp/protocjar17733680174955940357/bin/protoc.exe, /home/tools/proto/quality_detection.proto, /home/tools/proto/commons.proto,-I/home/tools/proto, -I/tmp/polyglot-well-known-types16154878821148829989, -I/home/tools/tool_com/abis_proto, --descriptor_set_out=/tmp/descriptor11234616606419094805.pb.bin, --include_imports]
- JMeter Java request
需要注意Java请求中引入的依赖与JMeter中原有依赖可能存在的冲突问题。
- K6
脚本无法复用gRPC client,必须在单次测试之内关闭client,否则会报错(见下图)
time="2025-09-23T12:24:41+08:00" level=error msg="GoError: context deadline exceeded: connection error: desc = \"error reading server preface: read tcp 192.168.0.31:57844->192.168.0.30:9000: use of closed network connection\"\n\tat reflect.methodValueCall (native)\n\tat default (file:///home/tools/k6_grpc.js:29:17(7))\n" executor=constant-vus scenario=default source=stacktrace
- Locust
执行测试时增加用户数并没有使并发增加,而且多次测试时发现结果几乎一致,当前的调用gRPC的方式存在一定问题。
测试总结
结合前文的测试结果,主观性的评分如下:
编辑
以上内容仅供参考,虽然场景足够简单,但也有其他因素干扰结果。
总体来说,每种工具方式都有自己的特点,在合适的场景中都有其独特的价值。如果哪位大神发现了文章中明显的错误,或者改良的方式,欢迎留言反馈。