「黑雲壓城城欲摧,甲光向日金鱗開」,唐朝詩人李賀字面上描繪了黑雲壓城的天然景象,但實際描述的是敵軍攻佔城池的人馬衆多,來勢洶洶,可是守城士兵依舊嚴陣以待,鬥志昂揚。這種攻守的戰爭場面,和服務器的高併發壓測十分類似。待壓測的服務器應用相似於待攻佔的城池,而測試軟件須要作的就是構造出成千上百萬的士兵,來攻佔服務器的應用。linux
如何構造出成千上百萬的攻城士兵,是高併發測試的關鍵。而傳統壓力測試工具設計的時候並非針對高併發測試設計的。針對高併發場景,傳統壓力測試工具每每自身是性能瓶頸。爲適應高併發趨勢,咱們設計了TCPBurn,用於無狀態協議的高併發壓力測試,瞬間能夠創造出任意多的攻城的精兵猛將。nginx
咱們以消息推送服務爲例,來模擬海量用戶併發場景。千萬併發鏈接測試相關的公開資料不多,聽說要達到C10M(千萬鏈接)併發,須要從根本上解決內核自身的問題。咱們的實驗但願驗證linux服務器環境下Nginx可否承受千萬鏈接的考驗。git
爲進行千萬併發鏈接測試,採用的軟硬件以下:github
服務器IP地址採用192.168.25.89。centos
TCPBurn採用IP欺騙的方式來模擬客戶端,須要爲客戶端選擇IP地址網段,這裏咱們選擇內網IP地址來欺騙Nginx。bash
客戶端虛擬機IP地址和TCPBurn客戶端所採用的IP地址關係以下:服務器
虛擬機和服務器具體關係圖以下(圖中省略192.168前綴):微信
圖中的城池即是咱們待測試的Nginx服務;咱們在城池的周圍創建了7個營地,每一個營地實際上是客戶端的虛擬機。接下來,咱們須要在每一個虛擬機上,經過TCPBurn來構建咱們的精兵猛將。每一個TCPBurn實例爲客戶端配置了254個IP地址,每一個可用的IP地址相似於營地中的兵團,而TCPBurn可利用的端口區間爲32768~65535,也就是每一個兵團的士兵可達到32768個。這麼計算下來,咱們使用TCPBurn,在每一個營地,垂手可得的便可以構造出254×32768=8323072個士兵,即800多萬併發鏈接。在咱們測試過程當中,每個營地會利用300萬個地址空間來構造300萬個客戶端併發鏈接,須要大概500多M內存,而2000萬併發鏈接,須要累計消耗3G多空間。session
服務器操做系統內核版本:併發
Linux 3.10.0-957.el7.x86_64
服務器CPU採樣配置以下圖:
服務器內存配置採樣圖:
從上圖能夠看出服務器可用內存大概是200G左右(爲性能考慮,海量併發測試不考慮swap空間),用來支撐2000萬併發鏈接應用。在這個配置下,理論上每個鏈接消耗資源不超過10k字節才能夠作到(包括內核+應用)。
虛擬機CPU採樣配置以下圖:
虛擬機內存狀況以下圖:
虛擬機的硬件條件足夠支持單個TCPBurn模擬300萬併發鏈接。
爲規避高併發帶來的系統參數問題,須要在以下方面進行配置:
參考第21講(IP CONNTRACK大坑,你跳不跳)內容,咱們在服務器端配置了iptables命令來規避IP conntrack坑(兩大坑,性能+鏈接沒法創建):
具體命令:
iptables -t raw -A PREROUTING -p tcp --dport 8080 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 8080 -j NOTRACK
這裏8080是Nginx監聽端口,意思是對出入8080端口的數據包不進行跟蹤。設置上述命令能夠下降內存和cpu資源消耗,而且也不會干擾測試的進行(21講會詳細講述)。
配置結果能夠經過iptables來查看,見下圖:
ulimit -HSn 1000000
須要注意上述命令只對本終端有效,建議修改/etc/security/limits.conf。
上述設置確保單個進程可以打開的句柄數量爲100萬。
因爲Nginx是多進程程序,因此能夠配置多個進程的方式來支持2000萬鏈接,理論上至少須要20個進程才能達到。
因爲TCPBurn採用了IP欺騙(原理相似流量複製工具TCPCopy,可參考課程TCPCopy相關部分),系統若是設置rp filter則會干擾測試的進行。
因爲被測試的服務器配置了rp filter過濾,爲簡單起見,在服務器端關閉全部rp_filter設置。
因爲TCPBurn採用IP欺騙的方式來模擬大量客戶端鏈接,須要在服務器端配置路由,使其響應可以回到發送請求的客戶端虛擬機上。
客戶端虛擬機利用了7臺虛擬機,所在的IP地址跟欺騙的IP網段地址對應關係以下:
192.168.25.121 <-------> 192.168.100.0
192.168.25.122 <-------> 192.168.101.0
192.168.25.123 <-------> 192.168.102.0
192.168.25.124 <-------> 192.168.103.0
192.168.25.125 <-------> 192.168.104.0
192.168.25.126 <-------> 192.168.105.0
192.168.25.127 <-------> 192.168.106.0
在 服務端進行路由設置:
route add -net 192.168.100.0 netmask 255.255.255.0 gw 192.168.25.121
route add -net 192.168.101.0 netmask 255.255.255.0 gw 192.168.25.122
route add -net 192.168.102.0 netmask 255.255.255.0 gw 192.168.25.123
route add -net 192.168.103.0 netmask 255.255.255.0 gw 192.168.25.124
route add -net 192.168.104.0 netmask 255.255.255.0 gw 192.168.25.125
route add -net 192.168.105.0 netmask 255.255.255.0 gw 192.168.25.126
route add -net 192.168.106.0 netmask 255.255.255.0 gw 192.168.25.127
上述路由設置的做用是請求從哪一臺虛擬機過來,其響應就回到哪一臺虛擬機。
咱們須要部署一個消息推送Nginx服務,用來支持2000萬模擬用戶的消息推送服務 。
具體安裝請參考官網:https://github.com/wandenberg/nginx-push-stream-module
Nginx配置參考下圖:
上圖中配置了20個進程,每個進程支持1048576個鏈接,理論上能夠支持2000萬併發鏈接(1048576×20 > 20000000)。
Nginx其它配置以下:
監聽端口採用8080端口。
消息推送相關配置參數見下面兩張圖:
完成上述步驟後,消息推送服務已經部署好,直接啓動Nginx:
./nginx
Nginx就能夠等待用戶發送請求過來。
tcpburn(TCPBurn是由tcpburn和 intercept組成)運行,首先須要intercept的配合(具體工做原理圖能夠參考TCPCopy部分課程)。
intercept工具的主要功能是截獲Nginx消息推送服務的響應數據包,配合tcpcopy和tcpburn工具來完成用戶會話回放。沒有intercept的配合,tcpburn就沒法工做。
intercept安裝參考官網github地址:https://github.com/session-replay-tools/tcpburn
若是編譯的時候遇到問題,通常是由於沒有安裝相應的libpcap開發庫,安裝上便可(centos上可使用命令:yum install libpcap-devel.x86_64)
運行命令以下:
須要注意的是,7臺虛擬機運行的intercept命令都同樣,具體命令以下:
./intercept -i eth1 -F 'tcp and src port 8080' -d
intercept命令參數說明以下:
-i 參數設置的是網卡設備名稱,因環境不一樣會有差別。
例如,下圖192.168.25.121對應的網卡設備是eth1,而Nginx返回給192.168.100.0網段的IP地址響應包都經過路由走向192.168.25.121(充當網關),通過的網卡就是eth1,因此-i參數選擇eth1:
-F 參數設置過濾條件,須要加引號,引號裏的內容相似tcpdump的過濾條件
-d 參數,設置-d表明以daemon方式運行
安裝tcpburn參考官網github地址:https://github.com/session-replay-tools/tcpburn
tcpburn不僞造原始數據,須要依賴外部抓包文件。
在服務器端,咱們採用以下命令來開啓抓包:
tcpdump -i any tcp and port 8080 -s 1500 -w 8080.pcap -v
而後咱們在另一個終端下面訪問Nginx服務:
這些訪問就會被tcpdump所捕獲。
咱們累計開啓5個終端,分別訪問以下:
curl -s -v --no-buffer 'http://192.168.25.89:8080/sub/my_channel_1'
curl -s -v --no-buffer 'http://192.168.25.89:8080/sub/my_channel_2'
curl -s -v --no-buffer 'http://192.168.25.89:8080/sub/my_channel_3'
curl -s -v --no-buffer 'http://192.168.25.89:8080/sub/my_channel_4'
curl -s -v --no-buffer 'http://192.168.25.89:8080/sub/my_channel_5'
而後ctrl+c關閉服務器tcpdump抓包,5個請求都被捕獲。利用wireshark來查看這些抓包數據,從下圖中咱們能夠看出抓包文件捕獲了5個請求做爲tcpburn回放的請求。
咱們把抓包文件8080.pcap放到每個虛擬機tcpburn運行目錄下面。
客戶端虛擬機必須關掉ip forward功能,不然那些回到虛擬機的響應數據包,會路由給真正的客戶端機器(具體參考TCPCopy欺騙部份內容)。
查看客戶端ip forward是否開啓的方法以下:
sysctl -a|grep ip_forward
若是net.ipv4.ip_forward = 1,那就必須在客戶端/etc/sysctl.conf文件裏面增長下面一行:
net.ipv4.ip_forward = 0
並執行sysct -p使其生效
在客戶端虛擬機上利用tcpburn命令發起鏈接請求。
在第一臺虛擬機(192.168.25.121)tcpburn命令以下:
./tcpburn -x 8080-192.168.25.89:8080 -f /xxx/tcpburn/sbin/8080.pcap -s 192.168.25.121 -u 3000000 -c 192.168.100.x
參數解釋以下:
-x 8080-192.168.25.89:8080表明複製抓包文件的8080端口請求到192.168.25.89機器的8080端口應用
-f 參數指定抓包文件的路徑
-s 參數指定運行intercept所在機器的IP地址(tcpburn與intercept一一對應)
-u 參數指定模擬用戶的數量,-u 3000000表明一個tcpburn實例模擬300萬用戶
-c 參數表明300萬用戶採用的IP地址列表是從192.168.100.0網段去獲取
因爲客戶端虛擬機和服務器都在同一個網段192.168.25.0,可能你會問客戶端IP地址爲何不採用這個網段的地址呢?首先咱們不想幹擾本網段的應用,其次單臺虛擬機採用的端口數是有限的。對服務器Nginx應用,因爲客戶端虛擬機TCP層的端口限制,最多模擬幾萬個鏈接,而TCPburn因爲繞開了TCP端口限制,採用IP欺騙的方式,能夠採用任意其它網段的IP地址,這樣就能夠利用海量的地址空間,從而爲海量用戶模擬打下基礎。這裏咱們採用192.168.100.0網段,理論上能夠利用254×32768=8323072(tcpburn採用的端口從32768開始)個鏈接,而咱們這裏只用了300萬個鏈接。
咱們逐個運行TCPBurn。每一臺虛擬機運行一個TCPBurn實例,包括tcpburn和intercept實例。intercept命令相同,而tcpburn命令在不一樣虛擬機的參數不一樣,須要修改-s參數和-c參數。下圖是運行了第6個tcpburn實例時的狀況,鏈接數量已經達到1600多萬。
此時的內存使用狀況呢?參考下圖,服務器還有60多G內存空間能夠利用。
此時的Nginx,狀況如何?Nginx開始大量報too many open files錯誤,每秒大概輸出這樣的日誌數量高達幾十萬,而咱們的鏈接數量只是以每秒幾千的速度緩慢增長。Nginx高頻繁輸出這樣的錯誤日誌,顯然是不合理的,並且這樣很快就會打爆磁盤空間。
此時,tcpburn運行過的命令以下圖:
第6個tcpburn實例並無運行完畢。理論上,第6個tcpburn運行完,鏈接數量可達到1800萬。
繼續觀察一段時間,服務器鏈接數量再也不增長,最終運行結果以下圖:
服務器鏈接數量達到1672萬後,就很難上升了,但從下圖的內存來看,還有50多G空閒內存,這說明Nginx遇到accept鏈接瓶頸了。
咱們繼續試驗,讓這1600多萬客戶端鏈接都接收一個消息推送,以便查看Nginx運行和內存狀況。
下圖咱們在服務器端機器對my_channel_1發送了Goodbye消息,根據Nginx返回結果,會有3344639個客戶端鏈接去接收這個消息。
同時咱們查看Nginx運行狀況,咱們發現下圖中的Nginx異常繁忙,由於有幾百萬的消息推送須要處理。
對5個channel進行消息推送(服務器端執行),命令以下圖:
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_1' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_2' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_3' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_4' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_5' -d 'Goodbye!'
5個消息推送完之後,每個客戶端鏈接都會收到一個推送消息。
根據下圖,能夠看出可用內存還有40多G空間,而整個消息推送過程,額外消耗了很多的內存。
經過此次測試,咱們發現Nginx以下的問題:
由於最終內存還有40多G,說明仍是有潛力的,咱們須要找出瓶頸所在。
經過以下命令嘗試繼續增長最大打開文件句柄數量
ulimit -HSn 1048580
-bash: ulimit: open files: cannot modify limit: Operation not permitted
ulimit -HSn 1048575
ulimit -HSn 1048576
ulimit -HSn 1048577
-bash: ulimit: open files: cannot modify limit: Operation not permitted
從中找出進程的最大文件句柄數量爲1048576,而後利用以下命令:
sysctl -a|grep 1048576
看看哪些參數設置了這個值,結果發現以下:
fs.nr_open = 1048576
fs.pipe-max-size = 1048576
其中pipe-max-size是pipe相關的,跟文件句柄最大支持數量沒有關係,而nr_open是真正可以改變最大文件支持數量的參數。
直接在/etc/sysctl.conf 文件裏面增長:
fs.nr_open = 2097152
這裏數量是原先的2倍大小。
執行:
sysctl -p
參數生效。
須要注意的是,只有內核2.6.25及以後能夠修改此參數。
而後咱們繼續設置以下命令:
ulimit -HSn 2000000
系統再也不報錯,這樣就能支持單個進程200萬併發鏈接了。
修改了系統參數後,須要繼續修改Nginx配置文件:
上圖理論上可支持3000萬鏈接,離2000萬有一段距離,應該不會再報too many open files錯誤。
從新啓動nginx和tcpburn,繼續新的一輪測試:
執行tcpburn的實例以下圖:
累計會有2050萬的併發鏈接請求發送到服務器。
最終服務器端的鏈接數量突破了2000萬鏈接,以下圖:
這個時候服務器空閒內存只有19G了,還能不能應對2000萬併發消息處理呢?
咱們繼續在服務器端發送下述請求到Nginx服務:
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_1' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_2' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_3' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_4' -d 'Goodbye!'
curl -s -v -X POST 'http://localhost:8080/pub?id=my_channel_5' -d 'Goodbye!'
咱們發現Nginx很是繁忙,以下圖:
利用free –m來查看內存狀況,結果以下圖:
爲何內存愈來愈多呢,咱們查看Nginx日誌,內容以下:
進程48887的Nginx進程退出了,並且仍是被殺退出的(signal 9)。
咱們看看服務器鏈接數量有沒有降低,利用ss –s查看,以下圖:
鏈接數量也降低了,緣由是Nginx進程48887被殺了。
咱們查看系統dmesg日誌(直接運行dmesg),從下圖咱們看出進程48887(nginx)被OOM了,也即由於內存吃緊被操做系統選擇性殺掉了,可用內存增長也就不足爲奇了。
整個測試到此爲止,測試2000千萬併發鏈接的目標順利達到。
經過此次測試,暴露了Nginx消息推送在極端海量併發狀況下的問題,並且爲了支持2000萬併發鏈接,還須要修改系統參數fs.nr_open,以支持ulimit -HSn更大數量的設置。
從此次測試過程當中,能夠證實Linux系統支持千萬併發是可行的,並且也無需不少配置。實踐是檢驗真理的標準,這句話仍是挺有道理的。
用戶若是感興趣,能夠去嘗試利用TCPBurn進行更加激進的性能測試,去探索海量鏈接場景下未知的Linux內核世界。btw,下面二維碼是「TCP相關問題經典案例分析」 知識星球,有興趣能夠加入,一塊兒探索TCP和應用的相關問題;也能夠經過qq羣:1013880537諮詢TCPBurn相關問題。
附得一技術公衆號二維碼,這裏後續會有很多精彩內容發佈