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;
}
}