看看tomcat是怎么限流的

561 阅读2分钟

这两天在看一本tomcat的书,书中讲到了无论是BIO还是NIO模式,Tomcat都会做连接限制的,想来也是这个道理,毕竟能让服务稳定运行才是最重要的。于是引出了一个陌生的类LimitLatch,下面就来看一看。 首先我们需要知道的是在tomcat中,有一个专门的Acceptor线程用来接受客户端连接,仅仅是用来接收,随后会将接收到的连接交给poller线程,如果了解过I/O多路复用的小伙伴因该心里都会知道,会有少量的线程来对可用连接的各种可读可写等事件进行监听,然后才会交给真正的业务线程。那这里这个poller线程就可以认为是专门用来监听事件的线程,而这个Accpetor线程仅仅只是用来接收连接。

//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection();

所有的连接到这里都会进入这个方法,这个endpoint是tomcat对底层连接和协议的一个抽象封装,这个方法注释很明了,如果到达连接上线,就会阻塞住,来看一看具体实现。

protected void countUpOrAwaitConnection() throws InterruptedException {
    if (maxConnections==-1) {
        return;
    }
    LimitLatch latch = connectionLimitLatch;
    if (latch!=null) {
        latch.countUpOrAwait();
    }
}

可以看到,如果我们的最大连接数设置为了-1,即表示不限制连接,默认是private int maxConnections = 8*1024;,然后就是获取了Limitlatch,他是在tomcat初始化的时候初始化的

if (connectionLimitLatch==null) {
    connectionLimitLatch = new LimitLatch(getMaxConnections());
}

然后看看具体的限流方法countUpOrAwait实现,进入Limitlatch会发现这个类代码很少,紧接着就会出现一个很熟悉的面孔AbstractQueuedSynchronizer,没错就是juc中的那个AQS,整体实现思路其实就是对一个共享变量,这里就是这个连接数进行原子自增,如果到了阀值就等待。

private class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1L;

    public Sync() {
    }

    @Override
    protected int tryAcquireShared(int ignored) {
        long newCount = count.incrementAndGet();
        if (!released && newCount > limit) {
            // Limit exceeded
            count.decrementAndGet();
            // <0表示尝试失败
            return -1;
        } else {
            return 1;
        }
    }

    @Override
    protected boolean tryReleaseShared(int arg) {
        count.decrementAndGet();
        return true;
    }
}

所以tomcat的这种限流方式是很简单的,只要自己对线程同步的知识熟悉一些也很容易想到这种方式,然后了解的多一点的还会想起信号量Semaphore,他们其实是一样的功能,过需要注意的是这种限流方式只是一种参考,他只是一个对总量的限制,而且是单机限制,同时没有时间窗口的概念,如果我们在业务开发中也有限流的需求,一定要注意使用场景,是否单机,是否需要时间窗口等。