微博百萬用戶分布式壓測實踐手記
“ 微博產品后端服務團隊在資源受限情況下基于Tsung提供了一套開箱即用的100萬用戶性能壓測工具套件,推動兩名能力和經驗都欠缺的中初級工程師,不但順利完成支持海量用戶的在線聊天室項目任務,同時保證了該業務系統整體處理性能可控并按照預期方式向前推進。 ”
需求 & 背景
前段時間我們為微博App增加一個視頻直播聊天室功能,這是一個需要支持海量級手機終端用戶一起參與、實時互動的強交互系統,建立在私有協議之上、有狀態的長連接TCP應用。和其他項目一樣,在立項之初,就已確定所構建系統至少能夠承載100萬用戶容量,以及業務處理性能最低標準。
當時研發力量投入有限,一名中級工程師 + 一名初級工程師,兩人研發經驗和實踐能力的欠缺不足以完成系統容量和處理性能的考核目標,這是擺在眼前最為頭疼問題。聊天室業務邏輯清晰并已文檔化,倒不用太擔心功能運行正確,除了自我驗證外,還會有QA部門幫忙校驗。這驅使我們在軟件工程角度去思考,如何通過行之有效的手段或工具去推動研發同學項目順利完成項目性能考核目標。
聊天室會話實例
延伸一下,新項目構建之初,自然要關注總體處理性能目標的。但從軟件生命周期角度考慮,還應該考慮到系統后續維護和迭代。所謂工欲善其事,必先利其器,因此我們需要提供一個完善的開箱即用、支持海量用戶的性能壓測基礎工具套件,既要能夠保證系統處理性能是可測量的,又要能夠為每一次版本發布時、常規迭代后再次驗證系統性能目標。帶著這一目標,下面將逐一展開我們如何構建百萬用戶性能壓測工具套件的過程以及注意事項。
性能貫穿于軟件生命周期
性能壓測工具選擇
團隊所能夠支配的空閑服務器數量幾乎為零,這是現狀。所負責業務線的大部分服務器配置為16GB內存、24個CPU核心左右,每時每刻都在跑著具體的業務。若留心觀察,一般晚上九、十點之后資源利用率高,而白天時間CPU、內存、網絡等資源大概會剩余30%-60%左右,頗為浪費,如何將這部分資源充分利用起來,是一個需要思索的問題。
針對100萬用戶這樣超大容量壓測需求,要求每臺壓測機要能夠負載盡可能多的用戶量。試想,若一臺壓測機器承載1萬用戶,那么就會需要使用到100臺,單純所需數量就是一件讓人很恐怖的事情。
若是單獨為性能壓測申請服務器資源,一是流程審批流程較為繁冗,二是因為壓測行為不是每時每刻都會執行高頻事件,會造成計算資源極大浪費。本著節約資源理想方式就是充分利用現有的空閑計算資源用于執行性能壓力測試任務。
【眾多選擇】
當前市面上能夠提供性能壓測的工具很多,選擇面也能很廣泛,下面我將結合實際具體業務逐一分析和篩選。
TcpCopy
線上引流模式,針對新項目或全新功能就不太合適了,業務層面若需要一定量的業務邏輯支持,很難做得到。
JMeter
Java編寫,在執行1萬個壓 測用戶線程時,CPU上下文切換頻繁,大量并發時會有內存溢出問題,很顯然也不是理想的選擇。
nGrinder
圖表豐富,架構很強大,堆棧依賴項太多,學習成本很高。
需要額外掌握Python等腳本語言,雖針對程序員友好,不是所有人都可以馬上修改。
數十個線程至少占用4GB內存,一臺機器上要模擬5萬個用戶,不但CPU上下文切換恐怖,16GB小內存機器更是遠遠滿足不了。
針對服務器資源充足的團隊,可以考慮單獨機器部署或Docker部署組成集群,針對我們團隊情況就不合適了,想免費做到是不可能的。
Tsung
完成同樣性能壓測功能,卻沒有第三方庫依賴,獨立一套應用程序。
雖然所測試服務是I/O密集型,但所占用內存不會成為瓶頸。
可能會觸及到Erlang語言,但我們現在工作用的語言就是Erlang,也就不存在什么問題了。
其他
綜合所述,Tsung默認情況下消耗低,可充分利用現有服務器空閑計算資源,線上多次實踐也證實資源占用始終在一個理想可控的范圍內,具有可讓百萬用戶壓測執行的費用成本降低為0的能力,這也是我們選擇Tsung的目的所在。
【為什么是Tsung?】
Tsung是一個有著超過15年歷史積累的性能壓測工具,本身受益于Erlang天生支持并發和分布式以及實時性的特性,其進程的創建和運行非常廉價,一臺機器上輕輕松松創建上百萬個進程。其提供一個用戶對應一個進程的隔離處理機制,單機支持虛擬用戶的用戶數量可支持若十萬、百萬級別,只要機器資源充足(但總體來講受制于受制于網絡、內存等資源,后面會具體解釋)。單機計算能力總是有限的,Tsung的目的就是要把多臺服務器橫向擴展成分布式集群,從而可以對外提供一致的海量性能壓力測試服務。
協議層,Tsung不但支持TCP/UDP/SSL傳輸層協議等,而且應用層協議,已支持諸如WebDAV/WebScoket/MQTT/MySQL/PGSQL/AQMP/Jabber/XMPP/LDAP等。默認情況下,開箱即用,憑借著社區的支持,市面上能夠找到的公開通用協議,都有相應官方或第三方插件支持。
和nGrinder相比,Tsung定位于提供一個強大的性能測試工具,在易用性和強大可擴展方面保持了一個平衡點。基于XML + DSL提供可配置、可編程的能力。比如我們可設置上一次的響應結果作為下一次請求內容,我們可配置多種業務協議、多個業務場景作為一個整體壓測等。盡力抽象所要壓測的情景吧,你不會失望的。
服務資源占用方面,以單機模擬5萬長連接用戶為例,有數據正常交互情況下,內存占用不到3GB,CPU占用不到兩核,十分經濟。
單機5萬長連接資源消耗
【Tsung的集群架構 & 流程】
知其然知其所以然,還是需要掌握Tsung集群大致流程的。這是一種強主從模型,簡單流程梳理如下:
Tsung主從架構圖
-
主節點(tsung_controller)通過SSH通道連接到從服務器啟動從節點運行時環境;
-
主節點通過RPC方式批量啟動從節點實例(tsung client);
-
主節點為每一個從節點啟動會話監控,控制會話速度,控制每一個壓測用戶進程ts_client生成速度;
-
從節點請求主節點具體業務進程,獲取會話指令以及會話具體內容;
-
從節點建立到目標壓測服務器的SOCKET網絡連接,開始會話;
-
主節點可以通過SSH通道連接到目標壓測服務器,啟動從節點,收集數據(可選)。
-
再深入一些,以MQTT協議為例,每一種具體協議的支持可分為文件解析和會話動作的執行,運作機制不復雜,當我們在需要時可以遵循接口約定實現私有協議支持,也不是難事。
100萬用戶壓測需要多少臺機器?
要執行100萬用戶的性能壓測,首先預估需要多少臺壓測服務器才能夠滿足要求。在Tsung中,一個壓測用戶對應于一個進程,一個進程默認會打開一條連接到服務器的TCP網絡連接。只要內存夠用(I/O密集型的應用一般吃內存),基于Erlang開發的應用程序,輕輕松松應對幾十萬、上百萬的進程不是問題。那么我們需要把注意力轉移到Linux網絡資源上。一臺服務器所能夠提供網絡IP地址數量和內存大小等主要因素,直接決定了能夠承載的對外建立的網絡連接數,下面我們把主要的影響因素一一列出并分析。
Tsung通用插件調用流程
【網絡四元組和總連接數】
說到網絡連接抽象層面構成元素,針對本機對外建立的一個TCP連接而言,需要使用到本機的IP地址(localip)和端口(localport),以及遠程的主機的IP地址(targetip)和端口(targetport),可以使用網絡四元組進行呈現一個連接的最小構成:
{local_ip,local_port,target_ip,target_port}
我司目前所使用的服務器操作系統大多為Centos 6.x版,受制于Linux內核限制,無論目標服務器連接地址如何變化,單機對外建立的連接數量是有限的:總連接數=本機可用IP地址數量×本機可用端口的數量。
明白了總連接數的計算方式,那么可以按圖索驥,從每一個計算因子上去考慮如何擴大其連接數。
注:若要調試TCP網絡四元組,諸位可使用bindp這個小工具: https://github.com/yongboy/bindp ,十分方便。
【Linux內核端口數量的限制】
眾所周知,在Linxu系統中,端口的數值范圍為無符號short類型,值范圍為1 ~ 65535。一般來講1 ~ 1023范圍默認只有Root用戶有權限使用,普通用戶可以使用區間范圍1025 ~ 65535,約6萬。
但你要考慮這中間很多的端口可能被已運行的程序占用,不妨打個折降低預期范圍,留有5萬左右的可用數值,以作緩沖。
我們可通過sysctl命令確認當前可用的端口范圍,以作參考:
bash sysctl -a | grep net.ipv4.ip_local_port_range
若范圍空間太小,比如1024 ~ 35525,那就需要主動擴大一下:
bash sysctl -w net.ipv4.ip_local_port_range="1024 65535" sysctl -p
【擴展閱讀:IP可用數量的延伸】
有三種比較經濟方式可擴展IP地址可用數量。
第一種,針對Linux內核 >= 3.9的Linux服務器而言,可設置SO_REUSEPORT內核參數進行支持;對外一個連接的四元組,將不再僅局限于本機IP地址和端口兩個元素,整個網絡四元組任何一個元素若有變化,都可以認為是一個全新的TCP連接,那么針對單機而言可用總連接數上限可以這樣計算:
總連接數 = 本機可用IP地址數量×本機可用端口的數量×遠程服務器可訪問IP地址數量×遠程服務器可訪問端口數量。
注:
-
遠程服務器指的是要壓測的業務服務器提供了多個訪問地址(IP地址:端口)訪問,但都指向同一個業務服務接口;
-
一般而言,一個業務服務只會開放一個IP地址和端口。
第二種方式,使用IP地址別名的方式,為本機綁定額外的可用的IP地址:
bash ifconfig eth0:1 10.10.10.101 netmask 255.255.255.0
注:所綁定的IP地址一定是可用的,否則會導致壓測機和被壓測的服務器之間無法成功建立連接。
第三種,可用考慮使用IP_TRANSPARENT(Linux kernel 2.6.28添加支持)特性支持。在你內存足夠大的情況下(比如擁有64GB ~ 128GB內存),假如壓測機A的IP地址為10.10.9.100,公司內網有一個IP段10.10.10.0暫時沒有被使用,你可以通過ip route工具設置一臺機器占用完整一個IP地址段支持。
壓測機需要為物理網卡eth1添加路由規則,以便能夠正常處理來自新增IP段的往返數據包:
bash ip rule add iif eth1 tab 100 ip route add local 0.0.0.0/0 dev lo tab 100
同時需要在被壓測的服務器上添加路由規則,方便在發送響應數據包時候能夠找到被路由的接口地址:
bash route add -net 10.10.10.0 netmask 255.255.255.0 gw 10.10.9.100
這樣一折騰,壓測機A的可用IP地址就多出來250多個全新的IP地址了。
注:其實,還可以虛擬若干個Docker實例達到這個目的。 有興趣的同學,可以進一步參考:Tsung筆記之IP地址和端口限制突破篇,以便獲得更多資料支持。
【內存因素】
針對大部分應用而言,Tsung默認網絡堆棧發送和接收緩沖區都是16KB,完全夠用了。一個網絡連接 = 一個用戶 = 一個進程,每進程業務占用約10KB,粗略算下來,一個不太復雜用戶邏輯上內存占用不到50KB內存。
按照這個方式計算,1萬用戶可占用500M內存,單機要支持6W用戶,再加上程序自身占用內存,整個Tsung實例大約會占用4GB內存。實際上測試中,在每臺壓測機分配5萬用戶情況下內存使用情況,可以見圖2。
【注意文件句柄】
一個網絡連接占用一個文件句柄,可用文件句柄數一定要大于對外建立的連接數。可使用ulimit查看限制的數量:
bash ulimit -Sn ulimit -Hn
若此值太小,根據實際情況調整,設置大一些,比如下面設置的30萬連接句柄限制,大于當前服務器日常對外提供的連接數峰值 + 壓測從機對外建立的連接數,有緩沖余地,可輕松應對大部分任務:
bash echo "* soft nofile 300000" >> /etc/security/limits.conf echo "* hard nofile 300000" >> /etc/security/limits.conf
還需要關注一下當前服務器總的文件句柄最大打開數量限制:
bash cat /proc/sys/fs/file-max
此值不能夠小于上面所設置的nofile的值,否則需要大一些:
bash sysctl -w fs.file-max=300000 sysctl -p
【百萬用戶壓測機組成】
說完影響因素,現在我們可計算一下,要壓測100萬用戶,理論上需要多少臺服務器支持:
-
每一個IP地址可以支持6萬個TCP連接同時打開,那么100萬個呢,100萬/6萬 ≈ 17個IP地址就夠了;
-
1萬用戶大約占用500MB內存,100萬用戶大概將占用500MB×100萬 / 1000MB ≈ 50GB內存。
總之理論上,一臺64GB內存服務器 + 17個可用IP地址,可以單獨完成100萬用戶的壓測任務。
注:我們忽 略了CPU資源的需求,一般壓測是I/O密集型,所耗費CPU資源不是很多,但也要小心,有時需要降低或關閉掉在壓測端進行一些高頻的數據校驗行為。
理論和現實總是有差距存在,要做100萬用戶的壓測,如上所述,我們沒有大量空閑服務器,有的是若干16GB小內存、單一內網IP地址的線上服務器,需要因地制宜:
-
留有緩沖余地,每一臺壓測機平均分配5萬個用戶;
-
1臺服務器用作壓測主機;
-
20臺線上服務器作為壓測機。
提前計算和準備好壓測使用的服務器,下面該進入設計業務壓測會話內容環節了。
設計業務壓測會話內容
緊密貼合業務設計壓測,會話內容將是需要考慮的重心。
【壓測連接信息】
怎么填寫壓測連接服務器地址、壓測從機,根據Tsung手冊操作即可。篇幅所限,不再贅述。
若是壓測單臺業務服務器,用戶每秒生成速率需要避免設置過大。因為業務型服務,一般以處理具體自身業務為先,在用戶每秒產生速率過快情況下,針對新建網絡連接的處理速度就不會太快。
比如我們實踐中設置每秒產生500個用戶對線上若干臺服務器壓測,可避免因為產生過快影響到線上其他具體業務。
實踐壓測環節中,可能需要考慮很多的事情:
-
會不會突然之間對LVS網絡通道產生影響,需要和網絡組同事進行協調;
-
會不會因為突然之間的壓力導致影響到其他現有服務;
-
一般建議,非封閉的網絡環境下將用戶每秒壓力產生速度設置小一點,保險一些。
【設計壓測會話內容】
壓力測試會話內容的編寫,有三個原則需要注意:模擬、全面和強度。
我們在設計壓測會話時,一定要清楚所開發系統最終面向的用戶是誰,其使用習慣和特征分別是什么,一定要盡可能的去復現其使用場景。其次,需要模擬的用戶會話內容要全面覆蓋用戶交互的各個方面,比如聊天室項目中,一個用戶從加入房間,中間流程包括點贊、打賞、光柱、發言等行為,中間間隔的心跳等,最后可選擇的退出行為,其業務場景,完整的體現在編寫的壓測場景中了。另外,針對業務場景特點,還針對每一個具體的行為,還要考慮其執行次數等,簡簡單單走一遍流程,也就失去了性能壓力測試的意義了。
下表是在壓測聊天室這個業務時壓測會話,包含了一個終端用戶的完整交互。
安裝和部署
Tsung安裝部署要求簡單:
-
所有壓測服務器上安裝有同樣版本的Erlang和Tsung
-
服務器之間SSH通道需要設置成免密鑰自動登錄形式
這針對一般的機房環境是沒有什么問題,但網絡環境是很復雜的,問題總是會多過設想。
【SSH不可用時的替代方案】
但SSH通道會被系統管理員出于安全考慮禁用,導致Tsung主節點無法啟動從節點,無法建立壓測集群。我司機房網絡環境就是如此,既然SSH不可使用,那么需要尋找/編寫一個替代者。
一般情況下,Tsung主節點啟動之后,從tsung.xml文件中讀取從機列表,進而啟動:
erlang slave:start(node_slave, bar, "-setcookie mycookie")
然后被翻譯為類似于ssh HOSTNAME/IP Command命令:
bash ssh node_slave erl -detached -noinput -master foo@node_master -sname bar@node_slave -s slave slave_start foo@node_master slave_waiter_0 -setcookie mycookie
這很好解釋了為什么壓測節點之間需要提前設置SSH免密鑰登錄了。
SSH為C/S模式,SSH Server是默認監聽22端口的一個守護進程,等待客戶端發送命令,解析執行,然后返回結果。明白了這個道理,我們可利用反向Shell機制打造一個SSH替代品。
首先我們需要在一個從節點上啟動一個守護進程:
bash ncat -4 -k -l 39999 -e /bin/bash &
主節點作為客戶端,比如我們想查詢遠程服務器主機名:
bash echo hostname | ncat 10.77.128.21 39999
一點都不復雜吧,但實際上還有很多的工作要做,比如自動斷開機制等,我已封裝了rsh_client.sh和rsh_daemon.sh兩個文件,可參考 https://github.com/weibomobile/tsung_rsh ,不再累述。
問題來了,如何結合Tsung使用呢?
第一步,在所有從機啟動守護進程:
bash sh rsh_daemon.sh start
第二步,需要在Tsung啟動時使用-r參數指定自定義的遠程終端:
bash tsung -r rsh_client.sh -f tsung.xml start
總之,這個SSH終端替代方案,已經在良好的運行在線上實際壓測中了,圖6為其部署結構。
注:其實不僅僅是替代,還可以在其上增加一些資源監控功能,我們已經這樣干了。
【IP直連特性支持】
Tsung還有一個使用不便的地方,從機必須配置主機名,用于主機啟動從機實例:
xml <client host="client20" maxusers="50000" weight="1"> <ip value="10.10.10.20"></ip> </client>
在主機名沒有內網DNS解析支持情況下,需要在/etc/hosts文件中手動配置主機名和IP地址映射關系,若是集群很大,維護成本高。如何辦呢,我增加了IP直連特性支持: https://github.com/weibomobile/tsung/ ,需要時檢出編譯即可使用。
這樣壓測從機可以直接填寫IP地址:
xml <client weight="1" maxusers="50000" host="client20"> <ip value="10.10.10.20"></ip> </client>
其次,在Tsung啟動時需要指定-I參數,并填寫壓測主機IP地址(可以通過Linux代碼自動獲 取):
bash tsung -I 壓測主機IP地址 -r rsh_client.sh -f tsung.xml start
這樣改造之后,讓Tsung分布式集群在復雜網絡機房內網環境下適應性向前邁了一大步。
性能壓測流程驅動
壓測之前,我們一般需要關注哪些東西呢,其實大家做法差不多,可以列一個清單:
-
添加計數器,可以發送到計數收集服務器,報表顯示等;
-
核心邏輯做好日志記錄,但日志記錄過多時,可能也會成為瓶頸,需要取舍日志等級等;
-
區分核心模塊和非核心模塊的在資源緊張時是否需要區別對待等。
壓測中,大家一般都是緊密盯著各項報表,查看服務器各項資源開銷等。有一點需要強調的是,盡量作為終端用戶一員,親身參與進去,這樣才能夠切身體驗并切切實實感受到此時服務的質量。
壓測后,復查各項報表,查看錯誤日志,結合剛才自身的體驗等,認真思考修改問題,然后繼續下一輪壓力測試啦。
小結
如上所述,我們在沒有空閑服務器情況下因地制宜,充分利用服務器空閑計算資源運行Tsung(有所增強、修改)分布式壓測集群,讓整體費用成本接近于零,同時也使之成為一項基礎工具套件,為研發的同學提供足夠多的支持。
在這一利器推動下,保證了我們整個聊天室項目的處理性能能夠按照預期方式向前推進,有時還會有一點小驚喜:
系統處理性能,由項目之初單機服務1萬用戶,優化到單機處理50萬用戶的飛躍;
-
從上線之初服務支持50萬用戶,到后面支持1000萬;
-
項目日常迭代,及時避免并修正了因額外功能引入的系統崩潰隱患;
-
激發了技術創新,期間收獲了3項技術創新專利;
-
勤奮有心的同學,雖然經驗欠缺,但被推著不停的發現問題、思考、解決問題,你可以看到他們成長的軌跡。
雖然看上去這一利器功不可沒,但工具就是工具,關鍵還要看使用的人,是否能夠一直堅持進行下去,是否可以貫穿于整個研發周期;但知易行難,需去除惰性,堅持下去,形成一種習慣,成為日常的業務流程(如圖7)的一個組成部分,否則只能是擺設。
性能壓測驅動
另外,希望能夠給同樣境遇的同學提供一種思路,或受限于SSH不可用而放棄搭建Tsung集群,或因地制宜需要稍作定制滿足特殊業務,或直接使用我們開源增強版Tsung避免走彎路,或者直接使用Docker等。總之使用Tsung做海量用戶的壓測,它不會讓你失望的。
來自:http://mp.weixin.qq.com/s/gbHtUgCtOGNuQQe1x8aFmQ