1. 程式人生 > >Linux中,Tomcat 怎麼承載高併發(深入Tcp引數 backlog)

Linux中,Tomcat 怎麼承載高併發(深入Tcp引數 backlog)

一、前言

這兩天看tomcat,查閱 tomcat 怎麼承載高併發時,看到了backlog引數。我們知道,伺服器端一般使用mq來減輕高併發下的洪峰衝擊,將暫時不能處理的請求放入佇列,後續再慢慢處理。其實作業系統已經幫我們做了一些類似的東西了,這個東西就是backlog。服務端一般通過 accept 呼叫,去獲取socket。但是假設我們的程式處理不過來(比如因為程式bug,或者設計問題,沒能及時地去呼叫 accept),那麼此時的網路請求難道就直接丟掉嗎?

當然不會!這時候,作業系統會幫我們放入 accept 佇列,先暫存起來。等我們的程式緩過來了,直接呼叫  accept 去 佇列取就行了,這就達到了類似mq的效果。

而 backlog,和另一個引數 /proc/sys/net/core/somaxconn 一起,決定了佇列的容量,演算法為:min(/proc/sys/net/core/somaxconn, backlog) 。

文章比較長,如果只需要結論,看第三章的總結即可,有時間的話,可以仔細看看正文、第四章的驗證部分。 如果只想知道怎麼設定這個值,直接跳到最後即可。

 

下面這篇文章,基礎原理講得很不錯。但是是外國人寫的,我這裡簡(tong)單(ku)翻譯一下,我也會結合自己的理解,做一些補充。原文連結:http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html

正文之前,查了下linux中的說明。在linux下,執行 man listen,可以看到:

 

int listen(int sockfd, int backlog);

DESCRIPTION
listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2).

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of

ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.

 

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection
requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored. See
tcp(7) for more information.

 我們著重看上面紅色部分,“backlog 的意義從linux 2.2開始,發生了變化。現在,這個引數指定了已完成三次握手的 accept 佇列的長度,而不是半連線佇列的長度。半連線佇列的長度可以通過 /proc/sys/net/ipv4/tcp_max_syn_backlog來設定”

 所以,下次,如果面試官問你這個引數的意思是什麼,那基本上答上面這句就沒問題了。

 但我們還是繼續拓展下。下面我用渣英語翻譯一下,權當鍛鍊了。

 

二、翻譯正文

1、兩種實現方式


當一個程式要進行監聽時,需要呼叫listen函式,此時,需要制定backlog引數。該引數,通常指定socket連線佇列的長度。

 

因為tcp連線的建立需要三次握手,因此,站在服務端的角度,一個到來的連線在變成established之前,需要經過一箇中間狀態SYN RECEIVED;

進入established狀態後,此時如果服務端呼叫accept操作,即可返回該socket。這意味著,tcp/ip協議棧要實現backlog佇列,有兩種選擇:

1、使用一個單獨的佇列,佇列的長度由 listen 呼叫的 backlog 引數決定。當收到一個 syn 包時,給客戶端返回 SYN/ACK,並將此連結加入到佇列。

當對應的 ACK 到達後, 連線狀態改變為 ESTABLISHED,然後即可移交給應用程式處理。 這意味著,佇列可以包含兩種狀態的連線: SYN RECEIVED 和 ESTABLISHED。

只有處於 ESTABLISHED 狀態的連線,才能返回給應用程式發起的 accept 呼叫。

2、使用兩個佇列,一個 SYN 佇列(或者叫做 半連線佇列) 和一個 accept 佇列(或者叫做 完全連線佇列)。 處於 SYN RECEIVED 狀態的連線將被加入到 SYN 佇列,後續當

狀態變為 ESTABLISHED 狀態時(比如三次握手中的最後一次 ACK 到達時),被移到 accept 佇列。 就像 accept函式的名字所表示的那樣, 實現 accept 呼叫時,只需要簡單地從

accept 佇列中獲取連線即可。 在這種實現方式下, backlog 引數決定了 accept 佇列的長度。

 

2、BSD 的選擇

歷史上, BSD 系統的 TCP 實現,使用第一種方式。 這種方式下,當 佇列達到 backlog 指定的最大值時, 系統將不再給客戶端發來的 SYN 返回 SYN/ACK 。 通常, TCP 實現會簡單地丟棄 SYN 包(甚至不會返回 RST 包),因此客戶端會觸發重試。 這個在  W. Richard Stevens 老爺子的 TCP/IP 卷三種的14.5節有講。值得注意的是, Stevens 老爺子解釋了, BSD 實際上確實用了兩個單獨的佇列, 但是它們表現的和一個單獨的,具有backlog引數指定的長度的佇列沒什麼差別。比如,BSD 邏輯上表現得和下面的表述一致:

佇列的大小是半連線佇列的長度 和 全連線佇列的長度之和。(意思是 sum = 半連線佇列長度 + 全連線佇列長度)

 

3、Linux 的選擇

在linux 上,事情不太一樣,在 listen 呼叫的 man page 上(就是咱們前言那一段):

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length forcompletely established sockets waiting to be accepted,

instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog.

 

這意味著, Linux非要對著幹,選了第二種方案: 一個 SYN 佇列, 大小由 系統級別的引數指定 ; 一個 accept 佇列, 大小由應用程式指定。

下面圖的意思是,服務端收到 SYN 後,會把該socket 放入 syns queue ,當該 socket 的 ack到來時, 服務端將其從 syns queue取出來,移到 accept queue 中。應用程式呼叫 accept 時,其實就是去 accept 佇列取。

 

 

4、linux實現中, accept 佇列滿了怎麼辦

 

有個問題是, 如果 accept 佇列滿了, 一個連線又需要從 SYN 佇列移到 accept 佇列時(比如收到了三次握手中的第三個包,客戶端發來的 ack),linux 下的該種實現會如何表現呢? 

這種場景下的程式碼處理在 net/ipv4/tcp_minisocks.c 中的 tcp_check_req 函式:

 child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
        if (child == NULL)
                goto listen_overflow;

 

對於 ipv4, 程式碼中第一行會最終呼叫net/ipv4/tcp_ipv4.c 中的 tcp_v4_syn_recv_sock:

ctcp_v4_syn_recv_sock的方法實現:

if (sk_acceptq_is_full(sk))
                goto exit_overflow;

 

這裡,我們看到有對accept 佇列的檢測。 exit_overflow 後的程式碼,會進行一些清理工作, 更新 /proc/net/netstat中的 ListenOverflows 和 ListenDrops 統計資訊 。 這會觸發 tcp_check_req 中 listen_overflow的執行:

## 看起來像我們的監聽者模式。。。
listen_overflow: if (!sysctl_tcp_abort_on_overflow) { inet_rsk(req)->acked = 1; return NULL; }

 

這個什麼意思呢? 意思是,除非  /proc/sys/net/ipv4/tcp_abort_on_overflow 設為 1 ,(這種情況下,會發送 RST 包),否則就什麼都不做。

(emmmm 。。。。。。有點偷懶?)

 

總結一下, 如果 linux 下的tcp實現,在 accept 佇列滿的情況下,收到了 三次握手中的最後一次 ack 包, 它就直接無視這個包。 一開始,看起來有點奇怪,但是記得, SYN RECEIVED 狀態下的 socket 有一個定時器。

該定時器的機制: 如果 ack 包沒收到(或者被無視,就像我們上面描述的這個情況), tcp 協議棧 會重發 SYN/ACK 包。(重發次數由 /proc/sys/net/ipv4/tcp_synack_retries  指定)

 

譯者這裡補充下:

答案: 若 /proc/sys/net/ipv4/tcp_abort_on_overflow = 0,服務端直接忽略該ack,因為服務端一直處於 SYN RECEIVED,觸發了定時器,該定時器會重傳 SYN/ACK 給客戶端,(不超過 /proc/sys/net/ipv4/tcp_synack_retries 指定的次數 );
如果 /proc/sys/net/ipv4/tcp_abort_on_overflow = 1, 則服務端會直接返回 RST,而不會重傳 SYN/ACK。

 

通過下面的網路跟蹤包(一個客戶端試圖連線到一個服務端的,佇列已達到最大 backlog 值的監聽 socket),我們看看會是神馬情況:

0.000  127.0.0.1 -> 127.0.0.1  TCP 74 53302 > 9999 [SYN] Seq=0 Len=0
  0.000  127.0.0.1 -> 127.0.0.1  TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
  0.000  127.0.0.1 -> 127.0.0.1  TCP 66 53302 > 9999 [ACK] Seq=1 Ack=1 Len=0
  0.000  127.0.0.1 -> 127.0.0.1  TCP 71 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
  0.207  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
  0.623  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
  1.199  127.0.0.1 -> 127.0.0.1  TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
  1.199  127.0.0.1 -> 127.0.0.1  TCP 66 [TCP Dup ACK 6#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
  1.455  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
  3.123  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
  3.399  127.0.0.1 -> 127.0.0.1  TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
  3.399  127.0.0.1 -> 127.0.0.1  TCP 66 [TCP Dup ACK 10#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
  6.459  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
  7.599  127.0.0.1 -> 127.0.0.1  TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
  7.599  127.0.0.1 -> 127.0.0.1  TCP 66 [TCP Dup ACK 13#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
 13.131  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
 15.599  127.0.0.1 -> 127.0.0.1  TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
 15.599  127.0.0.1 -> 127.0.0.1  TCP 66 [TCP Dup ACK 16#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
 26.491  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
 31.599  127.0.0.1 -> 127.0.0.1  TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
 31.599  127.0.0.1 -> 127.0.0.1  TCP 66 [TCP Dup ACK 19#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
 53.179  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491  127.0.0.1 -> 127.0.0.1  TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491  127.0.0.1 -> 127.0.0.1  TCP 54 9999 > 53302 [RST] Seq=1 Len=0

 

由於 客戶端的 tcp 協議棧收到了多個 SYN/ACK 包, 因此,它假設 ACK 包丟失了,於是進行重發。(可以看上面的 帶有 TCP dup ACK 的行)。

如果服務端監聽socket 的 backlog 值降低了 (比如,從 accept 佇列消費了一個連線,因此佇列變成未滿),而且, SYN/ACK 重試次數沒有達到最大值的情況下,那麼, tcp 協議棧就可以最終處理 客戶端發來的 ack 包, 將連線狀態從 SYN RECEIVED 改為

ESTABLISHED, 並將其加入到 accept 佇列中。 否則, 客戶端最終將會拿到一個 RST 包。(上圖示紅那行)

 

5、問題延伸

上面的網路抓包,也展示出另一個有趣的方面。 從客戶端的角度來說, 收到 服務端發來的 SYN/ACK 後,一直就處於 ESTABLISHED 狀態。 如果它發生資料 (不等待服務端發來的資料,畢竟是全雙工), 那麼資料同樣將會重傳。 TCP 慢開始演算法,會限制發出的包的數量。 (這裡意思是, 慢開始演算法下,一開始不會傳很多包,可能只傳一個,收到服務端的響應後,下一次傳2個,再一次傳4個,這樣指數級增長,直到達到一個值後,進入線性增長階段,因為服務端一直沒響應,就不會增大發送的包的個數,避免浪費網路流量)

 

另一方面, 如果客戶端一直等待服務端傳送資料,但是服務端的 backlog 一直沒有降低(一直沒能 accept 該客戶端), 那麼最終結果是, 客戶端該連線為 ESTABLISHED 狀態,在服務端,該連線狀態為 CLOSED。

 

還有一個我們沒討論的問題。 listen 的 man page 上說,每個 SYN 包將會新增到 SYN 佇列(除非佇列滿了)。 這個不完全準確。理由是,在 net/ipv4/tcp_ipv4.c 中的 tcp_v4_conn_request 函式中 (該函式負責 SYN 包的處理):

        /* Accept backlog is full. If we have already queued enough

         * of warm entries in syn queue, drop request. It is better than

         * clogging syn queue with openreqs with exponentially increasing

         * timeout.

         */

        if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {

                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);

                goto drop;

        }

這個意味著,如果 accept 佇列滿了, 那麼核心會隱式限制 SYN 包接收的速度。 如果收到了太多的 SYN 包, 部分會被丟棄。 在這種情況下, 由客戶端決定 進行重發,然後我們最終表現就和在 BSD 下的實現一樣。

總結下,為什麼linux的設計,會比傳統的BSD下的實現更為優越。 Stevens老爺子做了如下的觀點(這個翻譯太崩潰了。。。深奧。。。智商不行了。。。):

佇列長度將會達到backlog 限定值,如果全連線佇列滿了的話(比如,伺服器太忙,以至於程序無法足夠快地呼叫 accept 進行處理,好方便從 accept 佇列中騰出位置);或者,在半連線佇列滿了時,佇列長度也會達到 backlog。 後者就是http伺服器面臨的問題,當客戶端和服務端之間的往返時間較長時,(相對於什麼較長?相對於 新連線的到達速率),因為一個新的 syn 包 會佔據佇列,長達客戶端到服務端之間一次往返的時間。

當一個連線放入全連線佇列時,它幾乎總是空的, 因為當一個連線放入這個佇列時, accept 呼叫就返回了, 然後 伺服器將連線從佇列中移除。

 

4、Stevens老爺子的建議

Stevens老爺子的建議是,增加backlog的值。 假設一個程式,打算對backlog 進行調優,不僅要考慮它怎麼處理新建立的連線,也要考慮網路狀況,比如客戶端到伺服器的往返時間。

Linux的實現有效地分離了這兩個問題:

程式只需要負責調優 backlog,保證它能夠儘快地呼叫 accept,避免堆滿 accept 佇列;

系統管理員可以基於 網路狀況,對 /proc/sys/net/ipv4/tcp_max_syn_backlog 進行調優。

 

三、譯文的測試驗證

文章不太好理解,我查了些資料,參考https://www.cnblogs.com/xrq730/p/6910719.html後,我打算本地也進行一次驗證。

主要是通過ss命令、以及wireshark抓包,觀察這其中的細節。

1、服務端程式

首先,服務端程式為:

import java.net.ServerSocket;

public class ServerSocketClass {

    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8888, 5);

        while (true) {
            // server.accept();
        }
    }

}

 

2、客戶端程式


/**
* desc:
*
* @author : caokunliang
* creat_date: 2019/6/11 0011
* creat_time: 10:16
**/
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.locks.LockSupport;


public class ClientSocketClass {

private static Socket[] clients = new Socket[30];

public static void main(String[] args) throws Exception {
for (int i = 0; i < 1; i++) {
Socket socket = null;
socket = new Socket("192.168.19.13", 8888);
System.out.println("Client:" + socket + ", isConnected:" + socket.isConnected());
OutputStream outputStream = socket.getOutputStream();
outputStream.write('a');
}

// 阻止程式退出,因為退出的話,程式會直接傳送一個 RST 給伺服器,不能觀察 伺服器的 ACK/SYN 重傳
LockSupport.park();
}

}
 

值得注意的是,這裡,我們每次只發一次請求。

 

3、客戶端請求傳送5次,填滿 accept 佇列

觀察下面的圖,其中ss命令, 如果該條socket記錄為監聽埠,則Recv-Q 表示 accept 佇列中元素的個數, Send-Q 表示 accept 佇列中佇列的容量。

Recv-Q

Established: The count of bytes not copied by the user program connected to this socket.

Listening: Since Kernel 2.6.18 this column contains the current syn backlog.

Send-Q

Established: The count of bytes not acknowledged by the remote host.

Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.

 

啟動服務端程式,初始時,

 

每次我們執行客戶端,這裡便會加1。執行兩次後:

 

4、再次傳送連線請求

5次後,Recv-Q佇列將會變滿。如果此時再發送的話,按照參考部落格中的說法,是會報錯。但我這邊沒報錯,看 wireshark 抓包:首先看服務端發給客戶端的包,我們發現, 伺服器確實會一直髮送 SYN/ACK 給客戶端,一共發了5次(即為: /proc/sys/net/ipv4/tcp_synack_retries)。每次時間間隔加一倍。(參考退火演算法)

 

 

可以看到,服務端一直給客戶端傳送 SYN/ACK,所以,客戶端假設自己發出去的 ACK (三次握手的最後一次) 丟失了。於是會一直重發:

 

完整的互動如下:

 

 我們發現,這裡, 最後服務端會發送一個 RST ,但如果我們把客戶端程式改改:

//            OutputStream outputStream = socket.getOutputStream();
//            outputStream.write('a');

再次請求,進行抓包的話,會發現不會發送 RST 了:

 

 值得注意的是,在這種情況下,在客戶端檢視連線狀態是 ESTABLISHED ,而在伺服器端,查不到對應的連線資訊。這也就驗證了譯文中 “問題延伸” 一節的猜想。

客戶端:

 

伺服器:

  5、測試tcp_abort_on_overflow 引數

上面步驟都是在tcp_abort_on_overflow 為 false的情況下測試的, 這次我們開啟後,再用下面程式測試。

sysctl -w net.ipv4.tcp_abort_on_overflow = 1
import java.net.Socket;
import java.util.concurrent.locks.LockSupport;


public class ClientSocketClass {

    private static Socket[] clients = new Socket[30];

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 15; i++) {
            Socket socket = null;
            socket = new Socket("192.168.19.13", 8888);
            System.out.println("Client:" + socket + ", isConnected:" + socket.isConnected());
        }

        // 阻止程式退出,因為退出的話,程式會直接傳送一個 RST 給伺服器,不能觀察 伺服器的 ACK/SYN 重傳
        LockSupport.park();
    }

}

 

我們發起了15次連線,但是我們的 accept 佇列為5,按理說只能成功 5 +1 = 6個連線,剩下的9個連線都會無效。tcp_abort_on_overflow 的作用是,在 accept 佇列滿時,返回 rst。下面測試:

 

上圖可以看出,成功建立的只有6個,剩下的都被伺服器返回了 RST 。

 

 5、服務端正常accept時的連線情況

修改程式:將ServerSocketClass.java中的註釋行開啟,允許伺服器呼叫accept;客戶端迴圈次數改為20,看看伺服器上的情況:

 

 

 

 

四、簡單總結

backlog:該引數,每個程式可以在listen時自己設定,和另外一個引數( /proc/sys/net/core/somaxconn)一起,影響 全連線佇列的容量。 具體演算法是:min (backlog, /proc/sys/net/core/somaxconn ),最終可以建立的連線為 該值 + 1。

/proc/sys/net/ipv4/tcp_max_syn_backlog : 半連線佇列的容量。(os層面,只能設一個,由所有程式共享)

/proc/sys/net/ipv4/tcp_synack_retries :分兩種情況:

  1. tcp_abort_on_overflow = 0,服務端 accept 佇列滿了,客戶端發來 ack , 服務端直接忽略該ack。因此服務端一直處於 SYN RECEIVED,觸發了該狀態下的定時器,該定時器會重傳 SYN/ACK 給客戶端,(不超過 /proc/sys/net/ipv4/tcp_synack_retries 指定的次數 ), 超過後,服務端不再重傳,後續也不會再有任何動作;如果客戶端此時傳輸資料的話,服務端會返回 RST;
  2. tcp_abort_on_overflow = 1,服務端 accept 佇列滿了,客戶端發來 ack , 服務端直接返回 RST 。

 ps: 檢視、修改這些引數的簡單方法:

#檢視所有系統變數並查詢
[root@localhost ~]# sysctl -a |grep somaxconn
net.core.somaxconn = 128

# 設定系統變數
[root@localhost ~]# sysctl -w net.core.somaxconn=129
net.core.somaxconn = 129

 

五、 Tomcat 、nginx、redis中如何設定 backlog

1、tomcat

在tomcat 中, backlog 引數定義在org.apache.tomcat.util.net.AbstractEndpoint#backlog中,預設值為100。

    /**
     * Allows the server developer to specify the backlog that
     * should be used for server sockets. By default, this value
     * is 100.
     */
    private int backlog = 100;
    public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }

 

但是在實際處理中, 會由 Digester 框架,去解析 server.xml,解析到 connector 時, 首先新建 org.apache.catalina.connector.Connector,

然後開始設定屬性值:

 

當設定 acceptCount 時, 會呼叫 org.apache.catalina.connector.Connector#setProperty:

 

我們可以看看 replacements的定義:

     protected static HashMap<String,String> replacements =
         new HashMap<String,String>();
     static {
         replacements.put("acceptCount", "backlog");
         replacements.put("connectionLinger", "soLinger");
         replacements.put("connectionTimeout", "soTimeout");
         replacements.put("rootFile", "rootfile");
     }

 

所以,其實 connector 中 acceptCount 最終是 backlog 的值。

 

2、nginx

server{
        listen      8080  default_server backlog=1024;
}

 

3、redis

修改redis.conf

# TCP listen() backlog.
#
# In high requests-per-second environments you need an high backlog in order
# to avoid slow clients connections issues. Note that the Linux kernel
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
# in order to get the desired effect.
tcp-backlog 511

&n