公司有個匿名聊天的常規H5界面,運營向做一次 50W 的推送,為了能配合她的計劃,需要對該界面做一次壓力測試。
## 一、JMeter
  壓測工具選擇了[JMeter](https://jmeter.apache.org/),這是Apache的一個項目,它是用Java編寫的,所以需要先安裝Java的[SDK](https://www.oracle.com/java/technologies/downloads/#java8),選擇當前的操作系統。
:-: 
  隨后到JMeter官網下載應用程序,選擇 Binaries 中的[壓縮包](http://jmeter.apache.org/download_jmeter.cgi)。
:-: 
  在終端中進入解壓后的 bin 目錄,通過 sh jmeter 命令來啟動 JMeter。?
:-: 
  Don't use GUI mode for load testing:這段提示信息是不要在GUI界面進行壓力測試,GUI界面僅僅用于調試。
  程序會自動打開 JMeter 的界面,如果在 選項 -》 選擇語言 -》中文,那么有可能亂碼。
:-: 
  只需選擇 選項 -》 外觀 -》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
~~~
:-: 
## 二、實踐
  在正式開始壓測之前,也瀏覽了許多網絡資料作為知識儲備。
  首先需要理解Socket(套接字)的概念,它是對TCP/IP協議的封裝,本身并不是協議,而是一個調用接口,Socket連接就是長連接。
  在創建Socket連接時,可以指定傳輸層協議,通常選擇的是TCP協議,所以一旦通信雙方建立連接后就開始互發數據,直至連接斷開。
  而每個TCP都要占用一個唯一的本地端口號,但是每個端口并不會禁止TCP并發。
  然后去網上搜索了百萬長連接可能遇到的瓶頸,包括TCP連接數、內存大小、文件句柄打開數等,例如:
  每個TCP連接都要占用一個文件描述符,而操作系統對可以打開的最大文件數的限制將會成為瓶頸。
  如果對本地端口號范圍有限制(例如在1024~32768),當端口號占滿時,TCP就會連接失敗。
  網上給出了很多解決方案,大部分都是修改操作系統的各類參數。
**1)開始測試**
  上來就干,線程數直接填200以上。

  紅框中的字段含義如下所示:
* 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條,將這個數據作為當前每秒的線程數,模擬后一切正常。
  注意,線程數和服務器的并發量不能完全畫等號。
:-: 
  然后讓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,下面是結果圖。
:-: 
  其中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,所以不會出現那種無法打開文件的錯誤。
:-: 
  再詳細的分析暫時不會,還得先去系統的學習一下,然后再回來補充。
## 三、學習性能測試
  為了學習性能測試,特地在網上找了個專欄《[性能測試實戰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多,到一個時間后,就持續變少。下圖來自阿里云的日志,每次發消息我都會記錄一條日志。
:-: 
  我對上面的 -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),歡迎閱讀。

推薦一款前端監控腳本:[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年
- 2024年
- 日志
- 2020