Tổng Quan Về Kết Nối Và Quản Lý Luồng Trong Tomcat Của Spring Boot

Mỗi phiên bản Spring Boot và các container nội bộ khác nhau sẽ dẫn đến kết quả khác nhau. Bài viết này sẽ sử dụng Spring Boot 2.6.11 với Tomcat nội bộ phiên bản 9.0.65 làm ví dụ.

1. Tổng Quan Về Tomcat

Trong Spring Boot 2.6.11, các thiết lập mặc định của Tomcat như sau:

  • Kích thước hàng đợi kết nối: 100
  • Số kết nối tối đa: 8192
  • Số luồng công việc tối thiểu: 10
  • Số luồng công việc tối đa: 200
  • Thời gian chờ kết nối: 20 giây

Các cấu hình liên quan và giá trị mặc định:

server:
  tomcat:
    accept-count: 100
    max-connections: 8192
    threads:
      min-spare: 10
      max: 200
    connection-timeout: 20000
    keep-alive-timeout: 20000
    max-keep-alive-requests: 100

2. Kiến Trúc

Khi số lượng kết nối vượt quá giới hạn maxConnections + acceptCount + 1, các yêu cầu mới sẽ không được xử lý mà bị từ chối.

Tomcat mở rộng thread pool để nâng cao hiệu năng:

  • Luồng pool JDK: minThreads --> queue --> maxThreads --> Exception
  • Luồng pool Tomcat: minThreads --> maxThreads --> queue --> Exception

2.1 Kiến Trúc Luồng Pool JDK

2.2 Kiến Trúc Luồng Pool Tomcat

3. Các Tham Số Chính

3.1 AcceptCount

Kích thước hàng đợi kết nối tương đương với tham số backlog, lấy giá trị nhỏ hơn giữa hệ thống Linux (somaxconn) và Windows không có tham số cụ thể.

serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr, getAcceptCount());

3.2 MaxConnections

Số kết nối tối đa.

public void run() {    
  while (!stopCalled) { 
    connectionLimitLatch.countUpOrAwait();
    socket = endpoint.serverSocketAccept();
    socket.close();
    connectionLimitLatch.countDown();          
}

3.3 MinSpareThread/MaxThread

Số luồng công việc tối thiểu/tối đa.

public void createExecutor() {
  internalExecutor = true;
  TaskQueue taskqueue = new TaskQueue();
  TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
  executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS, taskqueue, tf);
  taskqueue.setParent((ThreadPoolExecutor) executor);
}

3.4 MaxKeepAliveRequests

Số lượng yêu cầu HTTP tối đa trong một kết nối giữ nguyên. Thiết lập thành -1 cho phép số lượng yêu cầu không giới hạn.

NioEndpoint.setSocketOptions();
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
Http11Processor.service(SocketWrapperBase<?> socketWrapper)

3.5 ConnectionTimeout

Thời gian tồn tại của kết nối, nếu không có yêu cầu nào đến sau thời gian này, kết nối sẽ bị đóng.

NioEndpoint.Poller#run()
long delta = now - socketWrapper.getLastRead();
long timeout = socketWrapper.getReadTimeout();
if (timeout > 0 && delta > timeout) {
    readTimeout = true;
}
long delta = now - socketWrapper.getLastWrite();
long timeout = socketWrapper.getWriteTimeout();
if (timeout > 0 && delta > timeout) {
    writeTimeout = true;
}

3.6 KeepAliveTimeout

Thời gian chờ yêu cầu HTTP tiếp theo trước khi đóng kết nối.

Http11InputBuffer.parseRequestLine()
if (byteBuffer.position() >= byteBuffer.limit()) {
    if (keptAlive) {
        wrapper.setReadTimeout(keepAliveTimeout);
    }
    if (!fill(false)) {
        parsingRequestLinePhase = 1;
        return false;
    }
    wrapper.setReadTimeout(connectionTimeout);
}

4. Các Thread Nội Bộ Chính

4.1 Acceptor

Chức năng là chấp nhận yêu cầu mạng và chuyển đổi thành NioSocketWrapper, sau đó đăng ký vào Poller.

public void run() {
    while (!stopCalled) {
        socket = endpoint.serverSocketAccept();
        endpoint.setSocketOptions(socket);
        poller.register(socketWrapper);
        events.add(new PollerEvent(socketWrapper));
    }
}

4.2 Poller

Đòi hỏi sự kiện, xử lý các yêu cầu theo cách NIO, phân bổ cho thread pool thực thi.

public void run() {
    while (true) {
        Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
        while (iterator != null && iterator.hasNext()) {
            SelectionKey sk = iterator.next();
            iterator.remove();
            NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
            if (socketWrapper != null) {
                processKey(sk, socketWrapper);
                processSocket(socketWrapper, SocketEvent.OPEN_READ/SocketEvent.OPEN_WRITE);
                executor.execute((Runnable)new SocketProcessor(socketWrapper, SocketEvent));
            }
        }
    }
}

4.3 TomcatThreadPoolExecutor

Thread pool thực hiện đọc/ghi kết nối, mở rộng từ thread pool JDK.

public void createExecutor() {
    internalExecutor = true;
    TaskQueue taskqueue = new TaskQueue();
    TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS, taskqueue, tf);
    taskqueue.setParent((ThreadPoolExecutor) executor);
}

public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
    private final AtomicInteger submittedCount = new AtomicInteger(0);

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        if (!(t instanceof StopPooledThreadException)) {
            submittedCount.decrementAndGet();
        }
    }

    @Override
    public void execute(Runnable command){
        submittedCount.incrementAndGet();
        try {
            super.execute(command);
        } catch (RejectedExecutionException rx) {
            if (super.getQueue() instanceof TaskQueue) {
                final TaskQueue queue = (TaskQueue)super.getQueue();
                try {
                    if (!queue.force(command, timeout, unit)) {
                        submittedCount.decrementAndGet();
                        throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                    }
                } catch (InterruptedException x) {
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(x);
                }
            } else {
                submittedCount.decrementAndGet();
                throw rx;
            }
        }
    }
}

public class TaskQueue extends LinkedBlockingQueue<Runnable> {
    private static final long serialVersionUID = 1L;
    private transient volatile ThreadPoolExecutor parent = null;
    private static final int DEFAULT_FORCED_REMAINING_CAPACITY = -1;
    private int forcedRemainingCapacity = -1;

    public TaskQueue() {}

    public TaskQueue(int capacity) {
        super(capacity);
    }

    public TaskQueue(Collection<? extends Runnable> c) {
        super(c);
    }

    public void setParent(ThreadPoolExecutor parent) {
        this.parent = parent;
    }

    public boolean force(Runnable o) {
        if (parent == null || parent.isShutdown()) {
            throw new RejectedExecutionException("taskQueue.notRunning");
        }
        return super.offer(o);
    }

    @Deprecated
    public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
        if (parent == null || parent.isShutdown()) {
            throw new RejectedExecutionException("taskQueue.notRunning");
        }
        return super.offer(o, timeout, unit);
    }

    @Override
    public boolean offer(Runnable runnable) {
        if (parent == null) {
            return super.offer(runnable);
        }
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) {
            return super.offer(runnable);
        }
        if (parent.getSubmittedCount() < (parent.getPoolSize())) {
            return super.offer(runnable);
        }
        if (parent.getPoolSize() < parent.getMaximumPoolSize()) {
            return false;
        }
        return super.offer(runnable);
    }

    @Override
    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
        Runnable runnable = super.poll(timeout, unit);
        if (runnable == null && parent != null) {
            parent.stopCurrentThreadIfNeeded();
        }
        return runnable;
    }

    @Override
    public Runnable take() throws InterruptedException {
        if (parent != null && parent.currentThreadShouldBeStopped()) {
            long keepAliveTime = parent.getKeepAliveTime(TimeUnit.MILLISECONDS);
            return poll(keepAliveTime, TimeUnit.MILLISECONDS);
        }
        return super.take();
    }

    @Override
    public int remainingCapacity() {
        if (forcedRemainingCapacity > DEFAULT_FORCED_REMAINING_CAPACITY) {
            return forcedRemainingCapacity;
        }
        return super.remainingCapacity();
    }

    public void setForcedRemainingCapacity(int forcedRemainingCapacity) {
        this.forcedRemainingCapacity = forcedRemainingCapacity;
    }

    void resetForcedRemainingCapacity() {
        this.forcedRemainingCapacity = DEFAULT_FORCED_REMAINING_CAPACITY;
    }
}

Thẻ: SpringBoot Tomcat thread-pool Configuration

Đăng vào ngày 25 tháng 9 lúc 21:15