一臺伺服器最大能支援多少條TCP連線?

Linksla發表於2023-02-08

一、一臺伺服器最大能開啟的檔案數

1、限制引數

我們知道在Linux中一切皆檔案,那麼一臺伺服器最大能開啟多少個檔案呢?Linux上能開啟的最大檔案數量受三個引數影響,分別是:

  • fs.file-max (系統級別引數):該引數描述了整個系統可以開啟的最大檔案數量。但是root使用者不會受該引數限制(比如:現在整個系統開啟的檔案描述符數量已達到fs.file-max ,此時root使用者仍然可以使用ps、kill等命令或開啟其他檔案描述符)
  • soft nofile(程式級別引數):限制單個程式上可以開啟的最大檔案數。只能在Linux上配置一次,不能針對不同使用者配置不同的值
  • fs.nr_open(程式級別引數):限制單個程式上可以開啟的最大檔案數。可以針對不同使用者配置不同的值

這三個引數之間還有耦合關係,所以配置值的時候還需要注意以下三點:

  1. 如果想加大soft nofile,那麼hard nofile引數值也需要一起調整。如果因為hard nofile引數值設定的低,那麼soft nofile引數的值設定的再高也沒有用,實際生效的值會按照二者最低的來。

  2. 如果增大了hard nofile,那麼fs.nr_open也都需要跟著一起調整(fs.nr_open引數值一定要大於hard nofile引數值)。如果不小心把hard nofile的值設定的比fs.nr_open還大,那麼後果比較嚴重。會導致該使用者無法登入,如果設定的是*,那麼所有使用者都無法登入

  3. 如果加大了fs.nr_open,但是是用的echo "xxx" > ../fs/nr_open命令來修改的fs.nr_open的值,那麼剛改完可能不會有問題,但是隻要機器一重啟,那麼之前透過echo命令設定的fs.nr_open值便會失效,使用者還是無法登入。 所以非常不建議使用echo的方式修改核心引數!!!

2、調整伺服器能開啟的最大檔案數示例

假設想讓程式可以開啟100萬個檔案描述符,這裡用修改conf檔案的方式給出一個建議。如果日後工作裡有類似的需求可以作為參考。

  • vim /etc/sysctl.conf

fs.file-max=1100000 // 系統級別設定成110萬,多留點buffer

fs.nr_open=1100000 // 程式級別也設定成110萬,因為要保證比 hard nofile大
  • 使上面的配置生效sysctl -p

  • vim /etc/security/limits.conf


// 使用者程式級別都設定成100完

soft nofile 1000000
hard nofile 1000000

二、一臺伺服器最大能支援多少連線

我們知道TCP連線,從根本上看其實就是client和server端在記憶體中維護的一組【socket核心物件】(這裡也對應著TCP四元組:源IP、源埠、目標IP、目標埠),他們只要能夠找到對方,那麼就算是一條連線。那麼一臺伺服器最大能建立多少條連線呢?

  • 由於TCP連線本質上可以理解為是client-server端的一對socket核心物件,那麼從理論上將應該是【2^32 (ip數) * 2^16 (埠數)】條連線(約等於兩百多萬億)
  • 但是實際上由於受其他軟硬體的影響,我們一臺伺服器不可能能建立這麼多連線(主要是受CPU和記憶體限制)。

如果只以ESTABLISH狀態的連線來算(這些連線只是建立,但是不收發資料也不處理相關的業務邏輯)那麼一臺伺服器最大能建立多少連線呢?以一臺4GB記憶體的伺服器為例!

  • 這種情況下,那麼能建立的連線數量主要取決於【記憶體的大小】(因為如果是)ESTABLISH狀態的空閒連線,不會消耗CPU(雖然有TCP保活包傳輸,但這個影響非常小,可以忽略不計)
  • 我們知道一條ESTABLISH狀態的連線大約消耗【3.3KB記憶體】,那麼透過計算得知一臺4GB記憶體的伺服器,【可以建立100w+的TCP連線】(當然這裡只是計算所有的連線都只建立連線但不傳送和處理資料的情況,如果真實場景中有資料往來和處理(資料接收和傳送都需要申請記憶體,資料處理便需要CPU),那便會消耗更高的記憶體以及佔用更多的CPU,併發不可能達到100w+)

上面討論的都是進建立連線的理想情況,在現實中如果有頻繁的資料收發和處理(比如:壓縮、加密等),那麼一臺伺服器能支撐1000連線都算好的了,所以一臺伺服器能支撐多少連線還要結合具體的場景去分析,不能光靠理論值去算。拋開業務邏輯單純的談併發沒有太大的實際意義。

伺服器的開銷大頭往往並不是連線本身,而是每條連線上的資料收發,以及請求業務邏輯處理!!!

三、一臺客戶端機器最多能發起多少條連線

我們知道客戶端每和服務端建立一個連線便會消耗掉client端一個埠。一臺機器的埠範圍是【0 ~ 65535】,那麼是不是說一臺client機器最多和一臺服務端機器建立65535個連線呢(這65535個埠裡還有很多保留埠,可用埠可能只有64000個左右)?

由TCP連線的四元組特性可知,只要四元組裡某一個元素不同,那麼就認為這是不同的TCP連線。所以需要分情況討論:

  • 【情況一】、如果一臺client僅有一個IP,server端也僅有一個IP並且僅啟動一個程式,監聽一個埠的情況下,client端和這臺server端最大可建立的連線條數就是 65535 個。

因為源IP固定,目標IP和埠固定,四元組中唯一可變化的就是【源埠】,【源埠】的可用範圍又是【0 ~ 65535】,所以一臺client機器最大能建立65535個連線

  • 【情況二】、如果一臺client有多個IP(假設客戶端有 n 個IP),server端僅有一個IP並且僅啟動一個程式,監聽一個埠的情況下,一臺client機器最大能建立的連線條數是:n * 65535 個

因為目標IP和埠固定,有 n 個源IP,四元組中可變化的就是【源埠】+ 【源IP】,【源埠】的可用範圍又是【0 ~ 65535】,所以一個IP最大能建立65535個連線,那麼n個IP最大就能建立 n * 65535個連線了

以現在的技術,給一個client分配多個IP是非常容易的事情,只需要去聯絡你們網管就可以做到。

  • 【情況三】、如果一臺client僅有一個IP,server端也僅有一個IP但是server端啟動多個程式,每個程式監聽一個埠的情況下(比如server端啟動了m個程式,監聽了m個不同埠),一臺client機器最大能建立的連線數量為:65535 * m

源IP固定,目標IP固定,目標埠數量為m個,可變化的是源埠,而源埠變化範圍是【0 ~ 65535】,所以一臺client機器最大能建立的TCP連線數量是 65535 * m個

  • 其餘情況類推,但是客戶端的可用埠範圍一般達不到65535個,受核心引數net.ipv4.ip_local_port_range限制,如果要修改client所能使用的埠範圍,可以修改這個核心引數的值。

  • 所以,不光是一臺server端可以接收100w+個TCP連線,一臺client照樣能發出100w+個連線

四、其他

  • 三次握手裡socket的全連線佇列長度由引數net.core.somaxconn來控制,預設大小是128,當兩臺機器離的非常近,但是建立連線的併發又非常高時,可能會導致半連線佇列或全連線佇列溢位,進而導致server端丟棄握手包。然後造成client超時重傳握手包(至少1s以後才會重傳),導致三次握手連線建立耗時過長。我們可以調整引數net.core.somaxconn來增加去按連線佇列的長度,進而減小丟包的影響

  • 有時候我們透過 ctrl + c方式來終止了某個程式,但是當重啟該程式的時候發現報錯埠被佔用,這種問題是因為【作業系統還沒有來得及回收該埠,等一會兒重啟應用就好了】

  • client程式在和server端建立連線時,如果client沒有呼叫bind方法傳入指定的埠,那麼client在和server端建立連線的時候便會自己隨機選擇一個埠來建立連線。一旦我們client程式呼叫了bind方法傳入了指定的埠,那麼client將會使用我們bind裡指定的埠來和server建立連線。所以不建議client呼叫bind方法,bind函式會改變核心選擇埠的策略


public static void main(String[] args) throws IOException {

    SocketChannel sc = SocketChannel.open();
   // 客戶端還可以呼叫 bind方法
    sc.bind(new InetSocketAddress( "localhost", 9999));
    sc.connect(new InetSocketAddress( "localhost", 8080));
    System.out.println( "waiting..........");
}
  • 在Linux一切皆檔案,當然也包括之前TCP連線中說的socket。程式開啟一個socket的時候需要建立好幾個核心物件,換一句直白的話說就是開啟檔案物件吃記憶體,所以Linux系統基於安全形度考慮(比如:有使用者程式惡意的開啟無數的檔案描述符,那不得把系統搞奔潰了),在多個位置都限制了可開啟的檔案描述符的數量。

  • 核心是透過【hash表】的方式來管理所有已經建立好連線的socket,以便於有請求到達時快速的透過【TCP四元組】查詢到核心中對應的socket物件

在epoll模型中,透過紅黑樹來管理epoll物件所管理的所有socket,用紅黑樹結構來平衡快速刪除、插入、查詢socket的效率

五、相關實際問題

在網路開發中,很多人對一個基礎問題始終沒有徹底搞明白,那就是一臺機器最多能支撐多少條TCP連線。不過由於客戶端和服務端對埠使用方式不同,這個問題拆開來理解要容易一些。

注意,這裡說的是客戶端和服務端都只是角色,並不是指某一臺具體的機器。例如對於我們自己開發的應用程式來說,當他響應客戶端請求的時候,他就是服務端。當他向MySQL請求資料的時候,他又變成了客戶端。

1、"too many open files" 報錯是怎麼回事,該如何解決

你線上上可能遇到過too many open files這個錯誤,那麼你理解這個報錯發生的原理嗎?如果讓你修復這個錯誤,應該如何處理呢?

  • 因為每開啟一個檔案(包括socket),都需要消耗一定的記憶體資源。為了避免個別程式不受控制的開啟了過多檔案而讓整個伺服器奔潰,Linux對開啟的檔案描述符數量有限制。如果你的程式觸發到核心的限制,那麼"too many open files" 報錯就產生了
  • 可以透過修改fs.file-max 、soft nofile、fs.nr_open這三個引數的值來修改程式能開啟的最大檔案描述符數量

需要注意這三個引數之間的耦合關係!

2、一臺服務端機器最大究竟能支援多少條連線

因為這裡要考慮的是最大數,因此先不考慮連線上的資料收發和處理,僅考慮ESTABLISH狀態的空連線。那麼一臺服務端機器上最大可以支援多少條TCP連線?這個連線數會受哪些因素的影響?

  • 在不考慮連線上資料的收發和處理的情況下,僅考慮ESTABLISH狀態下的空連線情況下,一臺伺服器上最大可支援的TCP連線數量基本上可以說是由記憶體大小來決定的。
  • 四元組唯一確定一條連線,但服務端可以接收來自任意客戶端的請求,所以根據這個理論計算出來的數字太大,沒有實際意義。另外檔案描述符限制其實也是核心為了防止某些應用程式不受限制的開啟【檔案控制程式碼】而新增的限制。這個限制只要修改幾個核心引數就可以加大。
  • 一個socket大約消耗3kb左右的記憶體,這樣真正制約服務端機器最大併發數的就是記憶體,拿一臺4GB記憶體的伺服器來說,可以支援的TCP連線數量大約是100w+

3、一條客戶端機器最大究竟能支援多少條連線

和服務端不同的是,客戶端每次建立一條連線都需要消耗一個埠。在TCP協議中,埠是一個2位元組的整數,因此範圍只能是0~65535。那麼客戶單最大隻能支援65535條連線嗎?有沒有辦法突破這個限制,有的話有哪些辦法?

  • 客戶度每次建立一條連線都需要消耗一個埠。從數字上來看,似乎最多隻能建立65535條連線。但實際上我們有兩種辦法破除65535這個限制

方式一,為客戶端配置多IP 方式二,分別連線不同的服務端

  • 所以一臺client發起百萬條連線是沒有任何問題的

4、做一個長連線推送產品,支援1億使用者需要多少臺機器

假設你是系統架構師,現在老闆給你一個需求,讓你做一個類似友盟upush這樣的產品。要在服務端機器上保持一個和客戶端的長連線,絕大部分情況下連線都是空閒的,每天也就頂多推送兩三次左右。總使用者規模預計是1億。那麼現在請你來評估一下需要多少臺伺服器可以支撐這1億條長連線。

  • 對於長連線推送模組這種服務來說,給客戶端傳送資料只是偶爾的,一般一天也就頂多一兩次。絕大部分情況下TCP連線都是空閒的,CPU開銷可以忽略
  • 再基於記憶體來考慮,假設伺服器記憶體是128G的,那麼一臺伺服器可以考慮支援500w條併發。這樣會消耗掉大約不到20GB記憶體用來儲存這500w條連線對應的socket。還剩下100GB以上的記憶體來應對接收、傳送緩衝區等其他的開銷足夠了。所以,一億使用者,僅僅需要20臺伺服器就差不多夠用了!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70013542/viewspace-2934439/,如需轉載,請註明出處,否則將追究法律責任。

相關文章