公司有个匿名聊天的常规H5界面,运营向做一次 50W 的推送,为了能配合她的计划,需要对该界面做一次压力测试。
## 一、JMeter
  压测工具选择了[JMeter](https://jmeter.apache.org/),这是Apache的一个项目,它是用Java编写的,所以需要先安装Java的[SDK](https://www.oracle.com/java/technologies/downloads/#java8),选择当前的操作系统。
:-: ![](https://img.kancloud.cn/c5/34/c534b152a089902645588b34d2eabc90_2380x396.png =800x)
  随后到JMeter官网下载应用程序,选择 Binaries 中的[压缩包](http://jmeter.apache.org/download_jmeter.cgi)。
:-: ![](https://img.kancloud.cn/c7/33/c733ede3662a1cb13f4a58642ae38bd6_1244x480.png =600x)
  在终端中进入解压后的 bin 目录,通过 sh jmeter 命令来启动 JMeter。
:-: ![](https://img.kancloud.cn/8c/72/8c7293c554a19637c6e30e94bc06ea3a_1444x312.png =800x)
  Don't use GUI mode for load testing:这段提示信息是不要在GUI界面进行压力测试,GUI界面仅仅用于调试。
  程序会自动打开 JMeter 的界面,如果在 选项 -》 选择语言 -》中文,那么有可能乱码。
:-: ![](https://img.kancloud.cn/20/1e/201e9b4dd2301685d19c3150730c116a_2688x1680.png =800x)
  只需选择 选项 -》 外观 -》System 或 Metal,就能避免乱码,网上有许多使用[教程](https://cloud.tencent.com/developer/article/1838392)可以参考。
  当测试计划都编写完后,保存,然后在终端输入命令,就能开始压测了,其中目录相对于bin,couples.jmx 是测试计划,webreport是统计信息。
~~~
sh jmeter -n -t ../demo/couples.jmx -l ../demo/result/couples.txt -e -o ../demo/webreport
~~~
:-: ![](https://img.kancloud.cn/56/31/56317da45d8ffd7cf415795ba6e905cb_3168x1256.png =800x)
## 二、实践
  在正式开始压测之前,也浏览了许多网络资料作为知识储备。
  首先需要理解Socket(套接字)的概念,它是对TCP/IP协议的封装,本身并不是协议,而是一个调用接口,Socket连接就是长连接。
  在创建Socket连接时,可以指定传输层协议,通常选择的是TCP协议,所以一旦通信双方建立连接后就开始互发数据,直至连接断开。
  而每个TCP都要占用一个唯一的本地端口号,但是每个端口并不会禁止TCP并发。
  然后去网上搜索了百万长连接可能遇到的瓶颈,包括TCP连接数、内存大小、文件句柄打开数等,例如:
  每个TCP连接都要占用一个文件描述符,而操作系统对可以打开的最大文件数的限制将会成为瓶颈。
  如果对本地端口号范围有限制(例如在1024~32768),当端口号占满时,TCP就会连接失败。
  网上给出了很多解决方案,大部分都是修改操作系统的各类参数。
**1)开始测试**
  上来就干,线程数直接填200以上。
![](https://img.kancloud.cn/79/89/798981c78f427ca90e3f8a704661fedf_1816x276.png =800x)
  红框中的字段含义如下所示:
* Label: 请求名称
* #Smaples: 请求计数,其中108.4是TPS(每秒处理的事务数)
* Average: 请求响应平均耗时
* Min: 请求响应最小耗时
* Max: 请求响应最大耗时
* Error %: 请求错误率
* Active:线程数(图中并未显示)
  查看报告页面,出现了多个错误,在网上查资源,做了些简单地挣扎,并没有得到好的解决办法。
~~~
Non HTTP response code: java.net.SocketException/Non HTTP response message: Connection reset
Non HTTP response code: javax.net.ssl.SSLHandshakeException/Non HTTP response message: Remote host terminated the handshake
Non HTTP response code: javax.net.ssl.SSLException/Non HTTP response message: java.net.SocketException: Connection reset
Non HTTP response code: java.net.SocketException/Non HTTP response message: Malformed reply from SOCKS server
~~~
  后面想想还是根据当前实际情况来吧,运营需要50W的推送,两小时内完成,平均每秒推送70条,将这个数据作为当前每秒的线程数,模拟后一切正常。
  注意,线程数和服务器的并发量不能完全画等号。
:-: ![](https://img.kancloud.cn/aa/6b/aa6b7730587fe9e7c746570dd83fce06_1010x618.png =400x)
  然后让4000个线程1分钟完成请求,配置Ramp-Up时间为60S,成功率是99.93%。
  图中的Ramp-Up时间指所有线程在多长时间(单位秒)内全部启动。例如500个线程10S,那么每秒启动 500/10=50 个线程,不写就是所有线程在开启场景后立即启动。
  再让5000的线程维持2分钟,配置Ramp-Up时间为120S,报无法创建新的本机线程的错误。
~~~
Uncaught Exception java.lang.OutOfMemoryError: unable to create new native thread in thread Thread[StandardJMeterEngine,5,main]
~~~
  为了解决此问题,期间走了很多误区,网上的很多资料都是说修改 jmeter.sh文件,像下面这样,但是改来改去仍然会报那错。
~~~
set HEAP=-server -Xms768m -Xmx768m -Xss128k
set NEW=-XX:NewSize=1024m -XX:MaxNewSize=1024m
~~~
  或者是用命令来修改本机的一些参数,像下面这样,但仍然无济于事。
~~~
launchctl limit maxfiles 1000000 1000000
sysctl -w kern.maxfiles=100000
sysctl -w kern.maxfilesperproc=100000
~~~
  后面看到篇文章说在macOS中,对单个进程能够创建的线程数量是有限制的,下面的命令可以读取最大值,例如本机是4096,但该参数是只读的,无法修改。
~~~
sysctl kern.num_taskthreads
~~~
  于是马上就改变策略,一番查找下来,了解到JMeter还提供了一种远程模式。
**2)远程模式**
  既然一台机器的线程数有限,那可以通过多台机器来模拟更多的虚拟用户,JMeter有一种远程模式可以实现这个方案。
  首先需要在bin目录中的 jmeter.properties 文件修改remote\_hosts参数,127.0.0.1改成本机地址,如下所示。
~~~
remote_hosts=192.168.10.10,192.168.10.46
~~~
  然后通过bin目录的create-rmi-keystore.sh生成rmi\_keystore.jks,windows的可以直接运行create-rmi-keystore.bat,mac需要运行create-rmi-keystore.sh文件,会问你一堆问题。
~~~
sh create-rmi-keystore.sh
~~~
  并且需要将rmi\_keystore.jks文件放置到从机的bin目录中。此时从机在开启sh jmeter-server时会报一个错误。
~~~
An error occurred: Cannot start. MacBook-Pro.local is a loopback address.
~~~
  修改jmeter-server,取消RMI\_HOST\_DEF的注释项,并将IP地址改成当前机器的。
~~~
RMI_HOST_DEF=-Djava.rmi.server.hostname=192.168.10.46
~~~
  一切准备就绪后,就可以使用压测命令了,与之前不同的是,需要加一个 -r 参数,其余照旧。
~~~
sh jmeter -n -t ../demo/couples.jmx -r -l ../demo/result/couples.txt -e -o ../demo/webreport
~~~
**3)继续测试**
  这次线程数量加到4000,加上从机,总共是1.2W个线程,Ramp-Up时间为60S,下面是结果图。
:-: ![](https://img.kancloud.cn/60/dc/60dc700e8e6206b8f7e61d0d7abebd4f_3032x874.png =800x)
  其中Throughput一列表示的是每秒处理的事务数(TPS),在此处也就是服务器的并发量。统计出21个错误,占比是0.17%。
~~~
Non HTTP response code: javax.net.ssl.SSLException/Non HTTP response message: Connection reset
~~~
  进到测试服务器,输入 ulimit -a 命令,open files 的数量有100多W,所以不会出现那种无法打开文件的错误。
:-: ![](https://img.kancloud.cn/34/a0/34a015618e5fbf80e5d4d3db93c4f34a_680x544.png =400x)
  再详细的分析暂时不会,还得先去系统的学习一下,然后再回来补充。
## 三、学习性能测试
  为了学习性能测试,特地在网上找了个专栏《[性能测试实战30讲](https://time.geekbang.org/column/intro/100042501)》,顺便记录了些基础概念。
**1)性能场景**
  基准性能场景,单交易容量,将每一个业务压到最大TPS。
  容量性能场景,将所有业务根据比例加到一个场景中,在数据、软硬件、监控等的配合下,分析瓶颈并调优。
  稳定性性能场景,核心就是时长,在长时间的运行之下,观察系统的性能表现。
  异常性能场景,宕主机、宕应用、宕网卡、宕容器、宕缓存、宕队列等。
**2)指标**
* RT:响应时间
* TPS:每秒事务数
* QPS:每秒SQL数
* RPS:每秒请求数
* Throughout:吞吐量
  所有相关的人都要知道TPS中的T是如何定义的。如果是接口层性能测试,T直接定义为接口级;如果是业务级性能测试,T直接定义为每个业务步骤和完整的业务流。
  对一个系统来说,如果仅在改变压力策略(其他的条件比如环境、数据、软硬件配置等都不变)的情况下,系统的最大 TPS 上限是固定的
~~~
TPS = (1000ms(1秒)/ RT(单位ms))x 压力线程数
~~~
  对于压力工具来说,只要不报错,我们就关心 TPS 和响应时间就可以了,因为 TPS 反应出来的是和服务器对应的处理能力,至少压力线程数是多少,并不关键。
**3)学习期**
  性能工具学习期:自己有明确的疑问。通常所说的并发都是指服务端的并发,而不是指压力机上的并发线程数,因为服务端的并发才是服务器的处理能力。
  性能场景学习期:如何做一个合理的性能测试,调整业务比例,参数化数据的提取逻辑。
  性能分析学习期:面对问题应该是我想要看什么数据,而不是把数据都给我看看。
  通过你的测试和分析优化之后,性能提升了多少?
  通过你的测试和分析优化之后,节省了多少成本?
**4)参数化**
  参数化测试数据的疑问:
* 参数化数据应该用多少数据量?
* 参数化数据从哪里来?
* 参数多与少的选择对系统压力有什么影响?
* 参数化数据在数据库中的直方图是否均衡?
  在性能场景中,我们需要根据实际的业务场景来分析需要用到什么样的数据,以便计算数据量。
  参数化时需要确保数据来源以保证数据的有效性,千万不能随便造数据。这类数据应该满足两个条件:
* 要满足生产环境中数据的分布;
* 要满足性能场景中数据量的要求。
## 四、Websocket Bench
  在这次的压测中,想要测试2000人在线,并且同时聊天,服务器能否完美处理。
  如果要访问页面模拟用户的行为,会比较麻烦,因为在聊天前需要做两步操作,第一步是确认协议,第二步是选择匹配范围,第三步才开始匹配用户开始聊天。
  若要两个用户匹配成功,首先需要都在线,其次是经纬度计算后的范围满足之前的配置。
  为了避免那么多繁琐的前置场景,我决定直接对socket进行压测,于是想到了[Websocket Bench](https://github.com/BedrockStreaming/websocket-bench)。
  它支持Socket.IO、Engine.IO、Primus等实时通信库的方法,经过简单的文档查阅后,开始编码,直接将官方demo复制修改。
~~~
module.exports = {
/**
* Before connection (optional, just for faye)
* @param {client} client connection
*/
beforeConnect : function(client) {
},
/**
* On client connection (required)
* @param {client} client connection
* @param {done} callback function(err) {}
*/
onConnect : function(client, done) {
// Socket.io client
client.emit('say', 100, {
id: 111,
avatar: 'http://www.pwstrick.com',
userId: 123,
msg: Date.now().toString(36) + Math.random().toString(36).substr(2),
msgType: 'text'
}, (msg) => {
console.log(msg);
});
console.count();
done();
},
/**
* Send a message (required)
* @param {client} client connection
* @param {done} callback function(err) {}
*/
sendMessage : function(client, done) {
done();
},
/**
* WAMP connection options
*/
options : {
// realm: 'chat'
}
};
~~~
  启动命令,-a 是指持久化连接总数 ,-c 是指每秒并发连接数 ,-g 是指要执行的JS文件,-k 保持活动连接,-o 是指日志的输出文件。
~~~
websocket-bench -a 2000 -c 2000 -g chat.js -k test-web-api.rela.me/chat -o opt.log
~~~
  开始运行后,并没有我设想的那样,实现2000人并发,TPS最多也就80多,到一个时间后,就持续变少。下图来自阿里云的日志,每次发消息我都会记录一条日志。
:-: ![](https://img.kancloud.cn/91/c5/91c553ad113af0f8dd83349b77863ace_1490x260.png =800x)
  我对上面的 -a 和 -c 的理解还有误差,不过也有可能是我本机限制了并发,之后就让QA在服务器上调试了。
*****
> 原文出处:
[博客园-Node.js躬行记](https://www.cnblogs.com/strick/category/1688575.html)
[知乎专栏-Node.js躬行记](https://zhuanlan.zhihu.com/pwnode)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020