스프링에서는 많은 요청을 기본적으로 어떻게 처리되는지 그 방식이 궁금했다.
예를 들어, JPA에서는 영속 컨텍스트 안에 데이터 처리 요청이 들어오면 인스턴스가 생기고 그런 인스턴스들을 관리하다 트랜잭션이 끝남과 동시에 영속 컨텍스트는 종료된다.
스프링에서는 자체 내장된 tomcat container(context)를 사용한다. 앞서 설명한 영속 컨텍스트와 기본적으로는 동일한 개념이다.
사용자가 서버에 접속하면 스레드(인스턴스)가 생성되고 이를 활용해 여러 작업을 하는 것이다.
방식은 다음과 같다.
- 1. 서버 시작시 Tomcat은 스레드의 컬렉션으로 Thread Pool을 생성
- 2. HttpServeletRequest(Client의 요청, HTTP통신 , ServerSocket에서 accept한 객체)시 요청들을 queue에 담는다.
- 3. 차례가 되면 한개의 Thread 할당. 이 때, 활성화된 Thread 만을 사용한다.
- ( 만약, 활성화된 Thread가 없고 queue도 꽉 찾다면 스레드 하나 더 활성화)
- ( 또한, queue도 꽉차고 활성화된 Thread도 없고 Thread Max도 넘었다면 더이상 연결 거부 처리(connection-refused))
- 4. 한가지의 기능 완료시 Client는 Thread를 반환
- 5. 항상 유효한 스레드는 min-spare로 유지하기 위해 노력.
"이것은 기본적으로 요청이 들어오면 생명을 주고 끝나면 destroy하는 servlet의 생명주기 관리를 베이스로 한다. 다만, 항상 이런식으로 처리하면 비효율적이므로 서버의 운영체제에 큰 부하를 주므로 항상 destroy하지 않고 Thread Pool을 생성해 관리한다."
기본적으론 Tocat의 스레드 Default 설정은 다음과 같다.
- Thread Pool의 스레드 최대 수 기본 값 (max) : 200
- Thread Pool의 최소한 유지되어야 할, 활성화 되어야 할 Thread 수 (min-spare) : 10
- 동시에 처리 가능한 클라이언트 요청 수 (max-connections) : 8192
- 요청을 담을 작업큐의 사이즈 (accpet-count) : 100
- 연결 지속 시간(서버가 요청을 처리할 수 있는 최대 시간, 즉 이 시간안에 서버는 요청을 처리해야 함. 넘어가면 오류처리, connection-timeout) : 20초
" 스레드 풀은 기본적으로 200개까지 스레드를 생성할 수 있고 10개 정도는 항상 활성화하여 바로 사용 가능하다. 또한, 8192개의 요청까지는 요청을 받아들이며 대기 큐의 사이즈는 100으로 지정한다.
다음의 설정을 변경해 위의 Default를 변경 가능하다.
server:
port:
tomcat:
max-connections:
accept-count:
connection-timeout:
threads:
max:
min-spare:
그렇다면 한가지 가정을 할 수 있다.
만약, 최대 스레드 수는 3개, 활성화된 스레드는 3개, 큐의 사이즈는 1개라고 가정했을 때, 5개의 요청시 마지막 요청은 연결 거부 처리가 되어야한다. 하지만 실제로 한번 실행을 해보면 그렇지는 않다.
이는 톰캣 9.0 부터는 Blocking I/O Connector 에서 Non Blocking I/O Connector로 변경 되었기에 가능하다. 실제로 확인해보려면 톰캣 버전 설정을 해서 실험해보면 좋을 것 같다.
Connector
- 소켓 연결 후 전달받은 데이터 패킷을 HttpServletRequest 객체로 변환해 Servlet 객체에 전달하는 역할 수행
BIO Connector
- 하나의 thread는 하나의 connection을 처리하는 방식, 요청 처리 중 다른 요청이 들어오면 Block
- 소켓 연결할 때, JAVA의 기본적인 I/O 사용한다.
- 스레드 풀에서 스레드를 꺼내와 소켓 연결을 하고 작업이 끝나면 소켓이 해제되어 스레드를 다시 스레드 풀로 보낸다.
- 동시에 사용되는 스레드 수 = 동시 접속자 수가 됨.
- 문제 : 많은 사용자를 받기 위해서는 많은 스레드가 필요하게 되는데 이는 반대로, 사용자가 없을 때는 사용되지 않는 스레드가 많아지고 낭비된다는 점에서 효율성이 떨어진다.
NIO Connector
- 다른 요청이 들어오면 Non Block 이며, 인터럽트로 빠져나갈 수 있다.
- Poller(Selector)라는 별도의 스레드가 소켓 커넥션을 처리한다.
- Poller는 소켓들을 캐시로 들고 있다가 소켓에서 데이터 처리가 가능한 순간에만 스레드를 할당하는 방식을 채택
- 데이터 처리가 가능한 순간에만 스레드를 할당하기 때문에 낭비가 줄어든다.
"Selector를 사용해 더 적은 스레드를 사용하고 max-connections 값까지 값을 접속자를 받는다. 활성화된 스레드 수가 모자르다면 max까지 스레드를 생성하고 기다리던 접속자들이 connection-timeout을 넘으면 접속이 종료된다."
'Server Development > Spring Basic' 카테고리의 다른 글
Spring - Interceptor (0) | 2023.04.06 |
---|---|
Spring - Jasypt (0) | 2023.04.06 |
Spring - AOP (0) | 2023.04.02 |
Spring - Exception (0) | 2023.04.01 |
Spring - Log(LogBack) (0) | 2023.04.01 |