通过jmeter对websocket后台做压测

后台使用java程序,通过springboot集成的stomp协议暴露websocket接口,所以下文测试过程会有特定的stomp报文,无需在意,关注流程即可

本次测试使用jmeter模拟大量用户接收群消息的场景,可覆盖连接数以及消息并发的压测

一、jmeter下载安装

下载地址
https://jmeter.apache.org/download_jmeter.cgi

下载zip安装包到本地解压

进入/bin目录,执行jmeter.bat启动

打开一个命令行窗口和一个GUI窗口,启动成功

可以通过Options -> Choose Language -> Chinese切换语言为中文

二、安装JMeter WebSocket Samplers 插件

jmeter默认不支持websocket协议,需要安装插件

下载地址
https://bitbucket.org/pjtr/jmeter-websocket-samplers/downloads/?spm=a2c4g.11186623.0.0.6cfd2486vZsEcu

下载jar包后,复制到jmeter目录的lib/ext下

重启jmeter,添加一个线程组

Test Plan右键 -> 添加 -> 线程(用户) -> 线程组

然后线程组右键 -> 添加 -> 取样器,可以看到websocket的组件,则插件安装成功

三、通过postman测试websocket连接

在开始jmeter压测前,先通过postman测试两个连接的群发消息功能正常

1、新建id为1002用户的websocket连接

输入后台握手地址,其中1002是sessionId,实际会由客户端随机生成

点击Connect

可以看到握手成功,后台响应了一个 o,此时已经完成协议升级

然后输入以下stomp指令,点击send

["CONNECT\nbusinessId:5\nmemberId:1002\naccept-version:1.1,1.0\nheart-beat:10000,10000\n\n\u0000","SUBSCRIBE\nbusinessId:5\nmemberId:1002\nid:sub-1\ndestination:/topic/12344321\n\n\u0000"]

这个指令是stomp协议的内容,可以不用关注,简单理解为向后台发送一条消息,订阅了一个名为12344321的队列
发送成功,服务端针对CONNECT指令响应了a["CONNECTED\nversion:1.1\nheart-beat:0,0\nuser-name:1002\n\n\u0000"]

2、新建id为1003用户的websocket连接

重复上述步骤,把地址栏以及stomp指令里的1002改成1003

3、通过1002用户发送群消息

在1002的连接上输入以下指令,向群组12344321发送一条消息

["SEND\nbusinessId:5\nmemberId:1002\ndestination:/websocket/sendToGroup\ncontent-length:110\n\n{"senderId":"1002","receiverId":"12344321","messageContent":"hello everyone","messageType":1,"receiverType":2}\u0000"]
发送后,由于1002自身也订阅了群组12344321,所以他也收到了服务端的推送
打开1003的窗口,同样收到了群消息
为方便测试,后台提供了接口,查看websocket连接数以及对应的IP和端口

通过本地cmd执行netstat命令,可以查看到这两个端口在使用中

websocket后端工作正常,接下来可以着手开始用jmeter压测

四、jmeter压测

先观察下插件提供的几个Websocket测试组件

Open Connection用于开启一个websocket连接,会完成协议升级

Single Write 用于向服务端发送数据,看名字,Single,只能发送一次,如果需要发送多次,可以建多个WebSocket Single Write Sampler

Single Read 用于接收服务端的推送,同样只能接收一次,如果要接收多次消息,要建多个

request-response 一个发送以及一个接收,相当于Write + Read

jmeter压测后可以保持连接活跃,但并不能像聊天窗口一样随时发送和接收消息,所以需要针对自己的测试场景组合这几个组件

新建计数器

先创建一个计数器,用以模拟不同用户建立连接

线程组右键 -> 添加 -> 配置元件 -> 计数器

设置计数器从2000开始,每次递增1,引用名称填sessionId,这个相当于变量名,后续可以用在请求地址或者请求参数里

新建WebSocket Open Connection

用于握手升级协议

线程组右键 -> 添加 -> 取样器 -> Websocket Open Connection

在新建的Websocket Open Connection中填入后台协议升级的接口IP、端口以及路径地址
其中路径里使用了变量${sessionId}

新建Websocket Single Write

在Connection里选中use existing connection,也就是会使用上面WebSocket Open Connection组件建立的连接

然后在Request data里填入stomp指令,完成用户身份绑定,以及订阅群组12344321

["CONNECT\nbusinessId:5\nmemberId:KaTeX parse error: Undefined control sequence: \naccept at position 12: {sessionId}\̲n̲a̲c̲c̲e̲p̲t̲-version:1.1,1....{sessionId}\nid:sub-1\ndestination:/topic/12344321\n\n\u0000"]

新建WebSocket Single Read

通过之前的postman测试可以发现,在握手成功以及CONNECT指令发送成功后,服务端都会有一个响应

(实际上spring内置的stomp集成,服务端会针对每一个客户端连接启动一个类似心跳的任务,每隔25秒推送一个报文h,这里为了方便测试,修改源码把这个任务停掉了,如果你的websocket服务端也有类似的定时推送,测试过程中需要留意,因为每个Read Sampler只能接收一次消息)

所以我们这里要建立3个Read Sampler组件,前2个用于接收服务端的响应,第3个用于等待接收群消息

Read Sampler的Connection同样要选择 use existing connection

为了方便测试,把这个3个Read Sampler名字后面分别加上 s e s s i o n I d − 握手、 {sessionId}-握手、 sessionId−握手、{sessionId}-订阅群组、${sessionId}-群消息

其中第3个Read Sampler由于要接收消息,将它的Response Timeout调大点

添加结果监听器

线程组右键 -> 添加 -> 监听器 -> 查看结果树

调整线程数

线程数调整为12000

执行测试

执行完后,结果里目前只有握手和订阅群组的响应,群消息还没发,所以第3个Read Sampler在等待中

通过后台接口查看连接数,包括了jmeter的12000个连接以及postman的2个连接

端口号

然后通过postman的用户1002再次发布群消息

jmeter里第3个Reader收到了消息
单机能建立的连接数受端口号限制,jmeter建立连接使用的端口号大多数从49000开始,到最大端口号65535,再加上其他进程占用的端口,瓶颈大概在16000

比如把线程数调整到20000,执行到16000就开始增长缓慢甚至卡住了,jmeter也有报错无连接可以复用

所以如果需要对服务器做连接数上限等的压测,比如数十万连接,就需要多台服务器配合了

踩坑记录

通过上面最后一张图,可以看到jmeter其实会复用一些连接,如果只通过WebSocket Open Connection来试图测试连接上限,会发现后台连接会短暂达到线程组的指定数字,但很快会降低到一个随机值,期间没有日志,但多测几次后,又有可能所有连接都保持活跃,线程组的线程数越多,这个现象越容易出现

通过对stomp的代码排查,发现部分连接被close掉的过程中,有一个异常被catch掉了,修改源码后抛出,可以在控制台看到一个EOF Exception

这个异常通常出现在读取的过程中意外遇到输入流末尾导致,放在这个场景里,就是jmeter在某个端口建立了连接A,后来又关闭A,在同一个端口上建立了连接B,在压测的过程中,这个变化非常快,服务端还没来得及做清理,于是在服务端看来,A和B都是某个IP的特定端口,是同一个socket连接,所以读取数据的时候就发生了混乱

至于为什么jmeter会复用这个端口,我的理解是jmeter的几个websocket组件,不管是Single Read Sampler还是Open Connection,其实都是一次性的,并不能持续的接收或者发送消息,那么当其执行完毕,jmeter认为其使命已经结束,在资源紧张的情况下,可以收回该端口了

通过单纯的OpenConnection测试,发现jmeter日志里确实有标明某线程已经Done并且Finished了

而通过本文测试接收群消息的方式,第3个Read Sampler的timeout时间很长,在群消息到来前,这个线程的任务并没有结束,所以也就不能清理并复用其端口了,于是所有的连接都通过不同的端口建立

相关推荐
石牌桥网管24 分钟前
OpenSSL 生成根证书、中间证书和网站证书
网络协议·https·openssl
sszmvb12346 小时前
测试开发 | 电商业务性能测试: Jmeter 参数化功能实现注册登录的数据驱动
jmeter·面试·职场和发展
半桶水专家7 小时前
用go实现创建WebSocket服务器
服务器·websocket·golang
阿尔帕兹7 小时前
构建 HTTP 服务端与 Docker 镜像:从开发到测试
网络协议·http·docker
FeelTouch Labs7 小时前
Netty实现WebSocket Server是否开启压缩深度分析
网络·websocket·网络协议
小码哥说测试7 小时前
接口测试用例设计的关键步骤与技巧解析!
自动化测试·测试工具·jmeter·职场和发展·测试用例·接口测试·postman
千天夜9 小时前
使用UDP协议传输视频流!(分片、缓存)
python·网络协议·udp·视频流
follycat10 小时前
[极客大挑战 2019]HTTP 1
网络·网络协议·http·网络安全
earthzhang202110 小时前
《深入浅出HTTPS》读书笔记(5):随机数
网络协议·http·https
xiaoxiongip66611 小时前
HTTP 和 HTTPS
网络·爬虫·网络协议·tcp/ip·http·https·ip