网易云信IM系统中的Web版使用了Socket.IO实现浏览器环境下的长链服务;区别于常规的长链服务,为该服务的压测提出了一些新的挑战,本文总结了测试过程中的一些收获供参考。
Part1测试工具选项
一、工具选型
Gatling
Node.js
JMeter
Java WebSocket
这些工具都是可以支持WebSocket协议的工具,主要从以下几个方面进行对比:
上手难易程度
测试资源开销
和性能测试平台结合的难易程度
二、对比过程
对比场景
(1)5000个用户,每秒钟发送100个登录请求,并保持连接
(2)5000个用户,每隔5s发送一条点对点消息,即发送消息频率1000
工具使用过程
- Gatling
工具简介:Gatling是一款基于Scala 开发的高性能服务器性能测试工具,它主要用于对服务器进行负载测试,并分析和测量服务器的各种性能指标。
工具语言:Scala语言
工具官网:http://gatling.io/docs/2.0.0-...
相关代码:
遇到的问题:
(1)scale语言比较陌生,脚本编写时困难较多,目前暂未实现隔一定时间,即发心跳又发消息的场景;
(2)无法和性能测试平台结合,系统整合成本比较高;
- Node.js + Socket.IO
工具简介:
Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node.js使用了一个事件驱动、非阻塞式I/O的模型,使其轻量又高效;而Socket.IO是一个基于Node.js架构体系的且原生支持WebSocket协议,可用作实时通信的软件包。Socket.IO为跨浏览器构建实时应用提供了完整的封装,且完全由JavaScript实现,语言更容易理解。
工具语言:JavaScript
工具官网:
• http://socket.io/docs/
• http://nodejs.cn/
相关代码:
遇到问题:
(1)多节点并发分布式控制
(2) 整合到现有的性能测试平台
- JMeter
工具简介:
JMeter是比较常见的性能测试开源工具,支持多种协议并且可以自定义java请求,更加灵活。
工具语言:
(1)Java
(2) JMeter xml脚本文件
工具官网:
http://jmeter.apache.org/user...
https://blog.flood.io/socket-...
相关代码:
遇到问题
JMeter的工具本上是同步的,如果建立1万个连接的话,需要启动1万个线程处理,因此不适合测试高并发长连接的场景。
- Jetty WebSocket Client
工具简介
Jetty提供了功能更强的WebSocket API,使用一个公共的核心API供WebSocket的服务端和客户端使用。
工具语言: Java
工具官网
http://www.eclipse.org/jetty/
相关代码
遇到问题
需要自己写并发控制
三、对比结果总结
因为用来做性能压测,所以考虑到压测客户端需要实现高并发请求,选择Gatling和Socket.IO做了简单的对比测试。
Part2 长链服务端性能问题的定位
如第一部分所述,我们最终选择用Socket.IO实现了并发压测的长链客户端,下面简单说明下长链服务器端测试的一些收获;
首先,简单描述下被测试服务器的功能
- 和前端Socket.IO客户端保持长连接;
- 解析前端JSON结构的数据包,并作二次封装后抓发给后端APP服务,并且将后端APP服务转发过来的响应包转换成为socketio可以识别的json数据包,并发送给客户端
为此,我们确定该服务主要的测试点为
- 测试服务器可以支撑的最大链接数:该测试点涉及到了TCP链接,以及我们需要注意哪些参数,才可以保证用户建立的连接数目不会因为限制而达不到目标。
- 每秒钟可以解析的包的数量:该测试点涉及到了网络流量,如果已经达到了网络流量的峰值时,会出现什么样的问题。
以下是这次测试过程中遇到的一系列问题:
问题1:最大连接数只能达到65K+
65535,对于程序员来说,这是一个很敏感的数字,因为一台服务器,限制的最大端口号即为65535,所以出现这个问题后:
第一反应:端口号不够用了。 分析认为Link服务作为服务端,对外提供的服务端口仅有一个,所以不存在服务端端口号不够用的情况;
第二反应:句柄数不够用了。文件句柄数相当于文件的标识符,建立一条socket连接,同样会使用一个文件句柄,而系统默认的单个进程使用的文件句柄为1024;
对于这种需要保持大量连接的服务来说,一般情况下都是需要修改文件句柄数的,文件句柄数修改的方法如下:
A)查看单个进程使用的最大文件句柄数的方法:
B)查看当前进程打开了多少个文件句柄呢:
C)修改Linux的最大文件句柄数限制的方法:
1)ulimit -n 65535
在当前session有效,用户退出或者系统重新后恢复默认值
2)修改用户下的.profile文件:在.profile文件中添加:ulimit -n 65535
只对当前用户有效
3)修改文件/etc/security/limits.conf,在文件中添加:
(立即生效-当前session中运行ulimit -a命令无法显示)
4)修改文件/etc/sysctl.conf添加:
运行命令:/sbin/sysctl -p 使配置生效
但是修改文件句柄的限制后,该问题依然没有得到解决。
这个时候想到可以使用dmesg查看系统日志,dmesg命令可以显示Linux内核的环形缓冲区信息,可以从中获得诸如系统架构、CPU和挂载的硬件,RAM等运行级别的大量系统信息,因此dmesg命令在设备故障的诊断方面是非常重要的。通过dmesg,查看到了如下问题:
从上面的信息可见conntrack表满了,那么conntrack表是做什么的?
nf_conntrack/ip_conntrack用来跟踪连接条目,会使用一个哈希表来记录 established 的记录
nf_conntrack 在 2.6.15 被引入,而 ip_conntrack 在 2.6.22 被移除,如果该哈希表满了dmesg命令就会出现:
nf_conntrack: table full, dropping packet
如何修改conntrack表的大小
到此为止,这个问题我们算是解决了,总结下,遇到的网络相关的知识点包括 文件句柄 和conntrack表。
问题2:在某些用例场景下,稳定连接只能建立3800+
这个问题的前提是某些时候,也就是说偶现的,这种情况基本上可以排除是应用程序内部的问题。那对于这个问题我们使用了哪些定位手段呢?
- watch和netstat 两个命令
watch -d 定期查看一些信息,默认是2s;
netstat -st | grep ignored 查看TCP连接的一些统计信息;
可见有SYNs to LISTEN sockets ignored在不停增加,这表明收到连接建立过程中的三次握手的ACK包,但是因各种原因(包括accept队列满) 创建socket失败;
- ss -ln : 查看进程对应的backlog的使用情况
backlog:简单理解来说,连接已经在TCP层建立成功(完成了三次握手的过程)但是还没有被应用程序所接受,这种情况下,该链接会存放到backlog这样一个缓冲队列内
通过上面这个命令可以看到应用程序的backlog队列已经满了,但是即便把这个值调整为1024,该队列还是会满的。
到这儿,我们的定位过程陷入了僵局,下一步该怎么定位呢?这期间我们查看了内存信息,CPU使用情况等,均没有找对地方;但是在定位过程中,我们发现,有时候JStack、JProfiler等工具都无法连上该服务了。
突然想到文件句柄是不是满了?又执行了以下几条命令:
/proc/{pid}这个目录下了对应了所有在运行的进程的相关信息,
通过查看/proc/{pid}/fd目录下的个数我们可以看到当前进程使用的文件句柄数有多少。
通过查看limits文件里面的值,可以看到系统对该进程的一些限制,不幸的是,出现这个问题的时候,max open files这个值被限制为了4096。
调整该值之后最终解决了这个问题
问题3:大量的连接建立不成功
当我们解决了一个又一个的问题后,突然发现高压力下存在大量的连接建立不成功的问题,主要表现在:
原来:每秒钟500个连接建立的请求时,5w个用户都可以建立成功;
现在:每秒钟500个连接建立的请求时,5w个用户只有4w左右的连接可以建立成功;
这个问题也花费了不短 的时间,我们使用了各种命令查看各种网络指标
通过netstat查看到send-q中有大量的消息堆积;
通过sar查看到有大量的重传;
总结起来还是TcpDump抓包比较效果更好,从测试的开始,我们就只抓这一条链路上的包,且发送方和接收方,双方都抓包:
然后通过Wireshark分析,可以看到
发送方/客户端:存在一定时间段发送出大量的重传包
接收方/服务端:客户端发送大量重传包的这个过程中,什么都没有收到
这基本说明网络有问题
然后我们通过netperf测试了下网络带宽的情况,发现这两台机器之间的网络上限只有21Mb,而且测试过程中的网络请求量是超过这个值的。由于是在云主机环境上,通过咨询云网络相关的同事发现,问题原因是因为云主机之间的网络QoS开启限制导致的,限制了每台云主机之间的网络带宽最高位21Mb,超过这个值则会被丢包。
TCP的链接状态图
最后总结下在定位类似的网络问题中一些常规的命令、工具和关注的方向
可以使用以下命令
推荐使用以下工具
重点关注以下指标
想要获取更多产品干货、技术干货,欢迎关注网易云信博客。