关于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。