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

相关文章
相关标签/搜索