關於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隊列的大小。服務器
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。併發
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
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參數值。
當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()處理業務需求了。
在三次握手過程當中,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表示有多少個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。