tomcat的acceptCount與maxConnections

關於tomcat的參數,有acceptCount、maxConnections、maxThreads、minSpareThreads這幾個參數比較容易混淆,這裏作一下澄清。html

不一樣層次

  • maxThreads、minSpareThreads是tomcat工做線程池的配置參數,maxThreads就至關於jdk線程池的maxPoolSize,而minSpareThreads就至關於jdk線程池的corePoolSize。apache

  • acceptCount、maxConnections是tcp層相關的參數。tomcat

圖片描述

tomcat有一個acceptor線程來accept socket鏈接,而後有工做線程來進行業務處理。對於client端的一個請求進來,流程是這樣的:tcp的三次握手創建鏈接,創建鏈接的過程當中,OS維護了半鏈接隊列(syn隊列)以及徹底鏈接隊列(accept隊列),在第三次握手以後,server收到了client的ack,則進入establish的狀態,而後該鏈接由syn隊列移動到accept隊列。tomcat的acceptor線程則負責從accept隊列中取出該connection,接受該connection,而後交給工做線程去處理(讀取請求參數、處理邏輯、返回響應等等;若是該鏈接不是keep alived的話,則關閉該鏈接,而後該工做線程釋放回線程池,若是是keep alived的話,則等待下一個數據包的到來直到keepAliveTimeout,而後關閉該鏈接釋放回線程池),而後本身接着去accept隊列取connection(噹噹前socket鏈接超過maxConnections的時候,acceptor線程本身會阻塞等待,等鏈接降下去以後,纔去處理accept隊列的下一個鏈接)。acceptCount指的就是這個accept隊列的大小。服務器

maxConnections(NioEndpoint$Acceptor)

這個值表示最多能夠有多少個socket鏈接到tomcat上。NIO模式下默認是10000.網絡

// --------------------------------------------------- Acceptor Inner Class
    /**
     * The background thread that listens for incoming TCP/IP connections and
     * hands them off to an appropriate processor.
     */
    protected class Acceptor extends AbstractEndpoint.Acceptor {

        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {

                // Loop if endpoint is paused
                while (paused && running) {
                    state = AcceptorState.PAUSED;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                if (!running) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    SocketChannel socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSock.accept();
                    } catch (IOException ioe) {
                        //we didn't get a socket
                        countDownConnection();
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // setSocketOptions() will add channel to the poller
                    // if successful
                    if (running && !paused) {
                        if (!setSocketOptions(socket)) {
                            countDownConnection();
                            closeSocket(socket);
                        }
                    } else {
                        countDownConnection();
                        closeSocket(socket);
                    }
                } catch (SocketTimeoutException sx) {
                    // Ignore: Normal condition
                } catch (IOException x) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), x);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }
            state = AcceptorState.ENDED;
        }
    }

這裏countUpOrAwaitConnection()判斷的就是當前的鏈接數是否超過maxConnections。併發

acceptCount(backlog)

在源碼裏頭是backlog參數,默認值爲100。該參數是指當前鏈接數超過maxConnections的時候,還可接受的鏈接數,即tcp的徹底鏈接隊列(accept隊列)的大小。app

backlog參數提示內核監聽隊列的最大長度。監聽隊列的長度若是超過backlog,服務器將不受理新的客戶鏈接,客戶端也將收到ECONNREFUSED錯誤信息。在內核版本2.2以前的Linux中,backlog參數是指全部處於半鏈接狀態(SYN_RCVD)和徹底鏈接狀態(ESTABLISHED)的socket的上限。但自內核版本2.2以後,它只表示處於徹底鏈接狀態的socket的上限,處於半鏈接狀態的socket的上限則由/proc/sys/net/ipv4/tcp_max_syn_backlog內核參數定義。socket

TCP三次握手隊列

  • client端的socket等待隊列:
    當第一次握手,創建半鏈接狀態:client 經過 connect 向 server 發出 SYN 包時,client 會維護一個 socket 隊列,若是 socket 等待隊列滿了,而 client 也會由此返回 connection time out,只要是 client 沒有收到 第二次握手SYN+ACK,3s 以後,client 會再次發送,若是依然沒有收到,9s 以後會繼續發送。tcp

  • server端的半鏈接隊列(syn隊列):
    此時server 會維護一個 SYN 隊列,半鏈接 syn 隊列的長度爲 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)  ,在機器的tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog下配置,當 server 收到 client 的 SYN 包後,會進行第二次握手發送SYN+ACK 的包加以確認,client 的 TCP 協議棧會喚醒 socket 等待隊列,發出 connect 調用。ide

  • server端的徹底鏈接隊列(accpet隊列):
    當第三次握手時,當server接收到ACK 報以後, 會進入一個新的叫 accept 的隊列,該隊列的長度爲 min(backlog, somaxconn),默認狀況下,somaxconn 的值爲 128,表示最多有 129 的 ESTAB 的鏈接等待 accept(),而 backlog 的值則應該是由 int listen(int sockfd, int backlog) 中的第二個參數指定,listen 裏面的 backlog 能夠有咱們的應用程序去定義的。

NioEndpoint的bind方法

@Override
    public void bind() throws Exception {

        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        serverSock.socket().bind(addr,getBacklog());
        serverSock.configureBlocking(true); //mimic APR behavior
        serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());

        // Initialize thread count defaults for acceptor, poller
        if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
        if (pollerThreadCount <= 0) {
            //minimum one poller thread
            pollerThreadCount = 1;
        }
        stopLatch = new CountDownLatch(pollerThreadCount);

        // Initialize SSL if needed
        initialiseSsl();

        selectorPool.open();
    }

這裏的serverSock.socket().bind(addr,getBacklog());的backlog就是acceptCount參數值。

tcp的半鏈接與徹底鏈接隊列

當accept隊列滿了以後,即便client繼續向server發送ACK的包,也會不被相應,此時,server經過/proc/sys/net/ipv4/tcp_abort_on_overflow來決定如何返回,0表示直接丟丟棄該ACK,1表示發送RST通知client;相應的,client則會分別返回read timeout 或者 connection reset by peer。

圖片描述

總的來講:能夠看到,整個TCP鏈接中咱們的Server端有以下的兩個 queue:

  • 一個是半鏈接隊列:(syn queue) queue(max(tcp_max_syn_backlog, 64)),用來保存 SYN_SENT 以及 SYN_RECV 的信息。

  • 另一個是徹底鏈接隊列:accept queue(min(somaxconn, backlog)),保存 ESTAB 的狀態,那麼創建鏈接以後,咱們的應用服務的線程就能夠accept()處理業務需求了。

SYN攻擊

在三次握手過程當中,Server發送SYN-ACK以後,收到Client的ACK以前的TCP鏈接稱爲半鏈接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK後,Server轉入ESTABLISHED狀態。SYN攻擊就是 Client在短期內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回覆確認包,並等待Client的確認,因爲源地址 是不存在的,所以,Server須要不斷重發直至超時,這些僞造的SYN包將產時間佔用未鏈接隊列,致使正常的SYN請求由於隊列滿而被丟棄,從而引發網絡堵塞甚至系統癱瘓。SYN攻擊時一種典型的DDOS攻擊,檢測SYN攻擊的方式很是簡單,即當Server上有大量半鏈接狀態且源IP地址是隨機的,則能夠判定遭到SYN攻擊了,使用以下命令可讓之現行:

netstat -nap | grep SYN_RECV

關於maxConnections與maxThreads

maxConnections表示有多少個socket鏈接到tomcat上。NIO模式下默認是10000。而maxThreads則是woker線程併發處理請求的最大數。也就是雖然client的socket鏈接上了,可是可能都在tomcat的task queue裏頭,等待worker線程處理返回響應。

小結

tomcat server在tcp的accept隊列的大小設置的基礎上,對請求鏈接多作了一層保護,也就是maxConnections的大小限制。

當client端的大量請求過來時,首先是OS層的tcp的accept隊列幫忙擋住,accept隊列滿了的話,後續的鏈接沒法進入accept隊列,沒法交由工做線程處理,client將獲得read timeout或者connection reset的錯誤。

第二層保護就是,在acceptor線程裏頭進行緩衝,當鏈接的socket超過maxConnections的時候,則進行阻塞等待,控制acceptor轉給worker線程鏈接的速度,稍微緩緩,等待worker線程處理響應client。

doc

相關文章
相關標籤/搜索