Thread pool을 생성한다
ServerProperties에서 확인할 수 있다server:
tomcat:
threads:
max: 200 # 생성할 수 있는 thread의 총 개수
min-spare: 10 # 항상 활성화 되어있는(idle) thread의 개수
max-connections: 8192 # 수립가능한 connection의 총 개수
accept-count: 100 # 작업큐의 사이즈
connection-timeout: 20000 # timeout 판단 기준 시간, 20초
기본 설정
- 대기큐: 연결 대기 중인 요청을 처리할 수 있는 큐로, 커넥션이 받아들여졌지만 아직 처리가 이루어지지 않은 요청이다
- 이 큐가 꽉 차면 새로운 연결요청이 거부된다
- 작업큐: 요청을 실제로 처리하는 스레드가 처리할 요청을 저장하는 큐이다.
- 이 큐가 꽉 차면 작업을 처리할 수 없으므로 추가 요청을 거부하거나 대기시킬 수 있다
- 대기큐와 작업큐는 다른 의미이다
- 예를 들어 8300개의 동시요청이 들어오면 8192개는 요청을 하고 100개는 대기를 하고 8개는 거절한다
- 200개의 스레드가 8192개의 요청을 처리하는 개념이다
- 작업큐의 용량은 무한이지만 최대 커넥션수의 8192이므로 그 이상 연결 요청이 오면 대기를 하거나 거절을 한다
public Tomcat() { this.maxConnections = 8192; // 최대 커넥션 수 this.acceptCount = 100; // 대기큐의 크기 this.maxKeepAliveRequests = 100; // 최대 연결유지 요청 수 } public static class Threads { private int max = 200; // 최대 스레드 수 private int minSpare = 10; // 최소 활성화 스레드 수 private int maxQueueCapacity = Integer.MAX_VALUE; // 작업큐의 최대 용량 }
Tomcat 3.2 이후부터 스레드풀을 활용했다
동작원리
- 요청이 들어올 때 스레드를 생성한다
1-1.core-size(내가 설정한 기본 스레드수)만큼 스레드를 생성한다- 유저 요청이 들어올 때마다 작업큐에 담아둔다
core-size의 스레드 중 유후상태(idle)인 스레드가 있다면 작업 큐에서 작업을 꺼내 스레드에 작업을 할당하여 작업을 처리한다
3-1. 만약 유휴상태인 스레드가 없다면 작업은 작업 큐에서 대기한다
3-2. 그 상태가 지속되어 작업큐가 꽉 찬다면, 스레드를 새로 생성한다
3-3.스레드 최대 사이즈에 도달하고 작업큐도 꽉 차게 된면 추가 요청에 대해서는 설정에 따라 거절, 예외 등을 한다- 태스크가 완료되면 스레드는 다시 유휴상태로 돌아간다
4-1. 작업큐가 비어있고core-size이상의 스레드가 생성되어 있다면 스레드를 제거한다
Tomcat 8.0부터 NIO Connector이 기본으로 채택되었다Tomcat 9.0부터 BIO Connector가 deprecate되었다NIO Connector가 등장했다Http11NioProtocol을 사용한다. 자바 NIO를 확인해보자Poller라고 하는 별도의 스레드가 커넥션을 처리한다동작 방식
- Acceptor: Socket Connection을 Accept한다
1-1. Socket에서 Socket Channel 객체를 얻어서 톰캣의 NioChannel 객체로 변환한다
1-2. NioChannel 객체를 PollerEvent라는 객체로 캡슐화하여 Event Queue에 넣는다
1-3. Acceptor는 Event Queue의 공급자, Poller Thread는 Event Queue의 사용자이다
- Poller는 NIO Selector를 사용하여 등록된 여러 채널을 관리하고, select 동작을 통해 읽을 수 있는 채널을 식별합니다.
2-1. Selector에는 다수의 채널이 등록되어 있고, Select 동작을 수행하여 데이터를 읽을 수 있는 소켓을 얻는다
2-2.Worker Thread Pool에서 이용할 수 있는 Worker Thread를 얻어서 해당 소켓을 Worker Thread에게 넘긴다- Poller에선 Max Connection까지 연결을 수락하고, Selector을 통해 채널을 관리하므로 작업큐 사이즈와 관계없이 추가로 커넥션을 거절하지 않고 받아 놓을 수 있다
- 스레드 또한 모자라다면 Max-Size까지 스레드를 추가한다
참고) https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests