Node.js躬行記(14)——壓力測試
- 2021 年 11 月 8 日
- 筆記
- Node.js躬行記
公司有個匿名聊天的常規H5介面,運營向做一次 50W 的推送,為了能配合她的計劃,需要對該介面做一次壓力測試。
一、JMeter
壓測工具選擇了JMeter,這是Apache的一個項目,它是用Java編寫的,所以需要先安裝Java的SDK,選擇當前的作業系統。
隨後到JMeter官網下載應用程式,選擇 Binaries 中的壓縮包。
在終端中進入解壓後的 bin 目錄,通過 sh jmeter 命令來啟動 JMeter。
Don’t use GUI mode for load testing:這段提示資訊是不要在GUI介面進行壓力測試,GUI介面僅僅用於調試。
程式會自動打開 JMeter 的介面,如果在 選項 -》 選擇語言 -》中文,那麼有可能亂碼。
只需選擇 選項 -》 外觀 -》System 或 Metal,就能避免亂碼,網上有許多使用教程可以參考。
當測試計劃都編寫完後,保存,然後在終端輸入命令,就能開始壓測了,其中目錄相對於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講》,順便記錄了些基礎概念。
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。
它支援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: '//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在伺服器上調試了。
參考資料:
websocket非同步高並發_websocket長連接壓力測試踩過的坑