百萬連接之路
前段時間接到某項目中關于虛擬機所在宿主機上最大支撐連接數的測試需求。應用場景類似于在物理機上運行著多個虛擬機,這些虛擬機對外提供服務,來自于任何地方的客戶端都可能向這些應用服務發起連接和請求。也許單個虛擬機并發的連接數十分有限,但對提供虛擬機服務的物理機或宿主機來說連接數就可能達到十萬、幾十萬甚至百萬。在這樣的情況下,宿主機是否能夠穩定運行呢?同時項目方也提出了明確的測試目標,支撐300萬連接,這就需要我們著手進行測試驗證了。
首先,我們先做一個假設,即這300萬連接里同時只有少數活動連接,測試場景可以簡化為保持300萬長連接的測試。明確了這一點,我們繼續分析測試可能出現的瓶頸點,是CPU、內存還是網絡?借鑒以往長連接或者消息推送類服務的測試經驗,由于保持長連接并不需要消耗過多CPU和網絡資源,只是占有系統的內存,我們決定把關注點主要集中在內存使用上,測試過程中也確實出現了一些內存相關的問題,這是后話了。
我們首先需要準備一個測試環境,一個可以支撐足夠連接的應用服務,大量的客戶端。很幸運我們可以借用杭研前臺技術中心基于node.js開發的開源游戲引擎pomelo作為服務端程序。使用java網絡API開發了一個簡單的客戶端程序。客戶端向服務器發起連接請求,成功后把連接對象保存在內存中。至此萬事具備,只欠執行測試了。
不急,我們先建立100萬連接試試。沒過多久第一個攔路虎就出現了,在客戶端日志里出現了java.net.NoRouteToHostException異常,將它作為關鍵字google一把,原來是/proc/sys/net/ipv4/ip_local_port_range配置的區間太小,端口耗盡導致,配置修改如下
pomelo@debian:~/socktest$ cat /proc/sys/net/ipv4/ip_local_port_range 2048 65500 |
可見單個客戶端ip只能建立6萬多連接,所以我們需要大約50個獨立ip發起300萬的連接,為簡單起見我們使用50臺虛擬機運行客戶端,而沒有采用單機多ip的方案。
繼續測試,再次被異常打斷,java.net.SocketException: Too many open files。這個有經驗的同學都應該了解,該修改ulimit了。改完繼續,可奇怪的是運行了一段時間后,又被同樣的異常中斷了,不是修改得很大了嗎?怎么又出了呢?確認修改有沒有生效,/etc/security/limits.conf文件是否保存,不會是這么狗血的問題吧。確認已經保存,檢查了客戶端進程的limit,/proc/pid/limits,發現open files竟然只有4096。百思不得其解。最后還是SA發現了問題的原因,原來是open files默認最大值1024*1024,我們修改時設置過大導致其溢出了,最后在/etc/security/limits.conf中添加如下兩行搞定
* hard nofile 1048576 * soft nofile 1048576 |
至此我們終于完成了100萬連接的階段性勝利,不能松懈, 一鼓作氣拿下300萬大關。等等,有人可能會問,服務端的open files也是配置1048576,就100萬多點,肯定不能支撐300萬連接吧。是的,open files是針對單一進程的限制,但我們跑的服務是多進程程序,所以不用擔心。另外,open files的最大值也能通過配置/proc/sys/fs/nr_open參數修改,這樣就能擺脫1048576的上限了;而系統中所有進程打開的文件數確實是需要配置的,通過fs.file-max修改。
接著我們遇到了這次測試中最可疑的問題之一。當建立完200萬連接以后,我們kill掉了一個服務進程,沒過多久,就發現部分運行客戶端的虛擬機不能ssh登陸了。通過vnc連接上后發現虛擬機CPU幾乎跑滿了,dmesg中存在Out of socket memory這樣的錯誤信息。此處省略一千字某SA大神的問題定位過程,本質上是由于服務端和客戶端之間存在4萬連接,服務進程掛掉后客戶端機器收到大量FIN包導致網絡相關內存溢出,而客戶端機器上存在一個使用curl的定時任務,網絡內存溢出引發其某個bug,進而引發CPU跑滿。因此調大net.ipv4.tcp_mem配置,注意該參數的單位是頁,而不是字節。
眼看著300萬連接的目標已經近在咫尺,可問題再次不請自來了。在漫長的等待后,我們用ss –s確認最終建立的連接數,但這個數值卻始終停留在280萬附近,照例的打開dmesg查看,發現了一大堆錯誤信息,如下為開始的一段
[531503.634391] TCP: too many of orphaned sockets [531503.634412] TCP: too many of orphaned sockets [531503.634432] TCP: too many of orphaned sockets [531503.634451] TCP: too many of orphaned sockets [531508.704084] net_ratelimit: 255864 callbacks suppressed [531508.704088] Out of socket memory [531508.704233] Out of socket memory [531508.704245] Out of socket memory |
簡單的調大net.ipv4.tcp_max_orphans參數,問題依舊。查看服務端日志,發現報錯
FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
查看服務進程數,確認有部分進程已不存在了,結合以上兩種日志不難猜到問題的根源,是由于服務進程掛掉,瞬間出現大量孤兒連接,進而導致網絡內存溢出引起。但服務進程為什么會莫名其妙地掛掉呢?咨詢了相關開發人員,由于pomelo是基于node項目的,需要通過編譯選項調整內存上限,修改重新編譯后測試,進程還是出現了內存溢出的情況,但明白了問題的本質,我們通過增加node進程數量,以減小單個進程的內存占用,可以繞過該問題。而node進程的內存限制問題還需要后續的確認。
到此為止,終于完成了300萬連接的目標,可喜可賀。簡單總結一下,連接數測試不同于常規的性能測試,不太關注TPS和響應時間等指標,主要是通過dmesg和日志中出現的異常信息定位問題,進而調整系統相應參數,這些參數大多與網絡、句柄數及內存有關。不僅僅是測試階段,日常運維這類系統也應該時刻關注這些。
相關文章:
posted on 2013-12-13 09:32 順其自然EVO 閱讀(604) 評論(0) 編輯 收藏 所屬分類: web 前端性能測試