千萬併發不是夢:TCPBurn併發測試

TCPCopy和Cetus開源主要做者
知識星球ID: 47406575,提供TCP經典案例分析課程
微信公衆號:得一技術
 
 
 

「黑雲壓城城欲摧,甲光向日金鱗開」,唐朝詩人李賀字面上描繪了黑雲壓城的天然景象,但實際描述的是敵軍攻佔城池的人馬衆多,來勢洶洶,可是守城士兵依舊嚴陣以待,鬥志昂揚。這種攻守的戰爭場面,和服務器的高併發壓測十分類似。待壓測的服務器應用相似於待攻佔的城池,而測試軟件須要作的就是構造出成千上百萬的士兵,來攻佔服務器的應用。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萬併發鏈接。

二、服務器端系統參數配置

爲規避高併發帶來的系統參數問題,須要在以下方面進行配置:

1)規避IP conntrack坑

參考第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來查看,見下圖:

圖片

2)規避文件句柄坑

ulimit -HSn 1000000

須要注意上述命令只對本終端有效,建議修改/etc/security/limits.conf。

 

上述設置確保單個進程可以打開的句柄數量爲100萬。

因爲Nginx是多進程程序,因此能夠配置多個進程的方式來支持2000萬鏈接,理論上至少須要20個進程才能達到。

 

3)關閉rp filter設置

因爲TCPBurn採用了IP欺騙(原理相似流量複製工具TCPCopy,可參考課程TCPCopy相關部分),系統若是設置rp filter則會干擾測試的進行。

 

因爲被測試的服務器配置了rp filter過濾,爲簡單起見,在服務器端關閉全部rp_filter設置。

 

4)路由設置

因爲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部署安裝

 

咱們須要部署一個消息推送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是由tcpburn和 intercept組成)運行,首先須要intercept的配合(具體工做原理圖能夠參考TCPCopy部分課程)。

intercept工具的主要功能是截獲Nginx消息推送服務的響應數據包,配合tcpcopy和tcpburn工具來完成用戶會話回放。沒有intercept的配合,tcpburn就沒法工做。

1)intercept安裝運行

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方式運行

2)tcpburn安裝

安裝tcpburn參考官網github地址:https://github.com/session-replay-tools/tcpburn

圖片

 

3)準備測試數據

 

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運行目錄下面。

 

4)關閉客戶端虛擬機ip forward功能

客戶端虛擬機必須關掉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使其生效

5、第一次衝擊2000萬併發失敗

在客戶端虛擬機上利用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以下的問題:

  1. 平白無故地記錄一些access log,而報錯以前沒有記錄
  2. 頻繁刷日誌,容易致使磁盤空間耗滿
  3. 負載不均衡,到1600多萬鏈接就很難接收新的鏈接了
  4. 千萬併發鏈接測試過程當中,經過pkill nginx有時候關不掉

 

由於最終內存還有40多G,說明仍是有潛力的,咱們須要找出瓶頸所在。

 

6、第二次衝擊2000萬併發成功

 

經過以下命令嘗試繼續增長最大打開文件句柄數量

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千萬併發鏈接的目標順利達到。

 

7、結束語

 

經過此次測試,暴露了Nginx消息推送在極端海量併發狀況下的問題,並且爲了支持2000萬併發鏈接,還須要修改系統參數fs.nr_open,以支持ulimit -HSn更大數量的設置。

 

從此次測試過程當中,能夠證實Linux系統支持千萬併發是可行的,並且也無需不少配置。實踐是檢驗真理的標準,這句話仍是挺有道理的。

 

用戶若是感興趣,能夠去嘗試利用TCPBurn進行更加激進的性能測試,去探索海量鏈接場景下未知的Linux內核世界。btw,下面二維碼是「TCP相關問題經典案例分析」 知識星球,有興趣能夠加入,一塊兒探索TCP和應用的相關問題;也能夠經過qq羣:1013880537諮詢TCPBurn相關問題。

 

附得一技術公衆號二維碼,這裏後續會有很多精彩內容發佈

相關文章
相關標籤/搜索