- 原文地址:Millions of active WebSockets with Node.js
- 原文作者:Alex Hultman
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:Mirosalva
- 校對者:portandbridge,sunui
僅使用消費級筆記本和一些 Wifi 資源便可提供大量的 WebSocket 服務
通過最新發布的 TypeScript web 服務工程 uWebSockets.js,我們看到它帶來的不僅有提升的效能,還有提升的記憶體利用率。對 Node.js 使用者尤其如此,所以為了演示我想在實際使用環境中開展大規模的測試。
我們計劃使用我那購買了 6 年的膝上型電腦,它具有 8GB 執行記憶體和 72Mbit 速率的 Wifi 網路介面卡(這是網路連結速度)的膝上型電腦。它還有一個 1Gbit 速率的乙太網介面卡,我們可以稍後使用。所有配置都是消費級的,在 2013 年購買後沒有任何硬體升級。這個筆記本將執行安裝了 uWebSockets.js v15.1.0 的 Node.js。
我們首先需要做一些 Linux 系統的配置工作 —— 主要是需要通過修改檔案 /etc/security/limits.conf(在你的系統上檔案路徑可能不同,我這裡用的是 Ubuntu 18.04 版本)來提升最大開啟檔案數量的限制。新增如下幾行:
* soft nofile 1024000
* hard nofile 1024000
複製程式碼
然後我們需要設定一些其他變數(同樣的,你的路徑可能不同):
sudo sysctl net.ipv4.tcp_tw_reuse=1
sudo sysctl fs.file-max=1024000
複製程式碼
然後你需要需要配置某一網段內的大約 50 個 IP 地址。對於我的 Wifi 介面卡,我新增了這行配置:
for i in {135..185}; do sudo ip addr add 192.168.0.$i/24 dev wlp3s0; done
複製程式碼
理論上,每個 IP 地址有 65k 個連線限制,但是實際上限值經常大約在 20k 左右,所以我們使用多個地址且使每個地址支撐 20k 個連線數(50 * 20 千 = 1 百萬)。
然後我使用命令 sudo -i 將 web 服務以 root 身份執行,這之後執行 ulimit -n 1024000 命令,接著對 node examples/WebSocket.js(在 uWebSockets.js 資料夾中)也這麼做。
真得就是這樣的。客戶端側做了類似的配置,但是顯然不需要設定多個 IP 地址。客戶端電腦執行一個由 uSockets 編寫的單執行緒 C 客戶端。這個測試的原始碼都是開源的,同時客戶端的程式碼是位於 uWebSockets/benchmarks 資料夾的『scale_test.c』。你可能需要為你自己的執行做一些小改動。
WebSocket 連線數量需要花幾分鐘才能達到 100 萬個,如果我們想做的改進的話可以增加每批次的連線數和使用多個執行緒的客戶端(諸如此類),但是這與我們對服務端感興趣的點無關。服務端執行在單個執行緒上並且在連線階段或之後 CPU 佔用率都很低。
首先,讓我們討論一下 5k 個關閉的連線。uWebSockets.js 被配置為丟棄和殺死所有閒置已超過 60s 的 WebSocket 連線。『idleTimeout』就被用到了,這意味著我們需要在每 60s 就要與每 100 萬個 WebSocket 連線主動傳送和接收一條 WebSocket 訊息。
你可以在這上面這張網路圖中看到與 ping 訊息相關的流量峰值。每秒最少有 16.7k 條 WebSocket 訊息需要到達伺服器 —— 都變少了之後我們開始關閉連線。
顯然我們通過 Wifi 網路沒有很好地滿足這個標準。我們是丟失了一些連線,但在一個沒有花哨配置的 WiFi 網路環境下仍存活 995k 個 WebSocket 連線卻是很酷的事情!
服務端的 CPU 使用率保持在 0–2% 範圍,使用者控空間記憶體使用大約為 500MB 而整體系統範圍的記憶體使用大約為 4.7GB。CPU 使用率或者記憶體使用一直都沒有出現服務端激增走勢,它始終處於完全穩定狀態。
好吧!那麼讓我們拿出大殺器吧 —— Ethernet。我們將伺服器和客戶端連線到 1Gbit 消費級路由器並重新執行測試:
結果是服務執行狀況穩定,而且沒有連線丟失,WiFi 網路穩定性不足但是 Ethernet 卻表現很好。為了保證每項都是穩定的,我讓客戶端和伺服器持續執行了一個小時,這樣沒有一個連線丟失,然後有約 1.2 億條 WebSocket 訊息(16.7k * 60 * 60 * 2):
每項都是穩定良好執行。事實上,我在執行服務的筆記本上寫著本文,並且被關閉的 socket 連線數量始終為 0,同時系統也是響應及時的。甚至我開啟一個簡單的遊戲的情況下服務還能讓連線繼續。
此時我們已經實現了一個非常酷的概念驗證場景。有一部分歸因於穩定的 Ethernet 連線,但當然很大程度上也依賴服務端軟體。任何其他的 Node.js 軟體棧都無法實現這一壯舉 —— 它們都不具備像這樣足以在筆記本上維持這麼多 WebSocket 連線的輕量級和高效能特點。你可以在系統變得無響應時停止 swap 分割槽交換,並且下面看到的這樣來停止獲取 ping 結果:
使用 uWebSockets.js,我們可以在這檯筆記本上執行幾十萬個 WebSocket 連線,但是超過 100 萬的常規連線則需要重新編譯具備不同限制的 Linux 核心,這也是我們把它作為邊界值的原因。
這裡我們不打算去研究底層的嵌入式 C 開發,並且我認為這是明智的選擇。只需啟動一個新應用例項,一臺新筆記本,通過這種方式繼續擴充套件你的問題。
如果你對這個軟體棧感興趣,有 I/O 擴充套件性問題,或者想要避免陷入許多常見陷阱,一定要聯絡我們,我們可以通過公司對公司的形式來研討問題。
感謝你的閱讀!
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。