전에는 Tomcat 내부의 Servlet
과 Servlet Container
에 대해서 공부했었다.
이번에는 Tomcat의 Servlet Container와 클라이언트를 연결시켜주는Connector
에 대해서 공부해보자.
출처 : https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat
Connector를 공부하기 앞서 잠깐 thread에 대해서 알아보자.
스레드란 프로세스 내에서 실제로 작업을 수행하는 주체라고 하며 여러개의 스레드를 사용하는 것을 멀티 스레드라고 한다.
작업을 수행할때마다 스레드를 생성하지 않고 프로그램 실행에 필요한 스레드를 미리 만들어 저장하는 곳이다.
Tomcat에는 기본적으로 Thread Pool이 생성되어있고 설정되어있다.
# application.yml (적어놓은 값은 default)
server:
tomcat:
threads:
max: 200 // 생성할 수 있는 thread의 총 개수
min-spare: 10 // 항상 활성화 되어있는(idle) thread의 개수
max-connections: 8192 // 수립가능한 connection의 총 개수
accept-count: 100 // 작업큐의 사이즈
connection-timeout: 20000 // timeout 판단 기준 시간, 20초
port: 8080 // 서버를 띄울 포트번호
connection-refused
요류를 반환한다.그만큼 Tomcat은 사용자의 요청을 처리하기 위해 스레드는 필수적인 존재이다. 이 요청을 처리하기 위해 필요한 Connector에도 스레드가 사용되는데, 이 스레드를 효율적으로 처리한다면 한번에 많은 사용자의 요청을 처리할수 있게 된다.
출처 : https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat
Connector는 기본적으로 클라이언트의 요청을 받아 Servlet이 처리할 수 있는 HttpServletRequest로 바꿔주는 역할을 한다.
위에서 설명한 Connector는 2가지 방법의 Connector가 있다. Blocking IO방법인 BIO Connector와 NonBlocking IO방법인 NIO Connector가 있다.
BIO Connector는 요청시 하나의 thread가 할당되어 요청을 처리한다. 하지만 connection이 닫힐때 까지 하나의 thread는 특정 connection에 할당되어 있기 때문에 idle(아무것도 하지 않는) 상태로 낭비되는 시간이 많아져 효율적으로 자원을 사용하지 못하게된다.
이를 해결하기 위해 NIO Connector가 등장하게 되었다.
출처 : https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat
JIoEndPoint의 Acceptor는 클라이언트 요청이 들어왔을때 port listener
를 통해 소켓을 얻는다.
JIoEndPoint의 Worker는 Acceptor에서 socket을 받아서 Http11ConnectionHandler에서 Http11Processor를 얻고 요청을 처리한다.
HttpRequest에 맞는 서블릿을 바인딩
하는데 사용.
Http11Processor에 의해 호출되며 worker스레드가 할당된 소켓의 Http요청을 HttpServletRequest object
로 변환하는 역할을 한다.
출처 : https://hadev.tistory.com/m/28
BIO Connector에서 요청이 들어오면 요청 당 하나의 thread가 할당되게 된다.
이렇게 되면 connection이 닫힐 때 까지 하나의 thread는 idle한 상태로 유지되어 다른 요청시 해당 thread는 사용할수 없어 낭비되는 시간이 많아지게 된다.
출처 : https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat
출처 : https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat
NIoEndPoint는 Http11Protocol에서 소켓을 얻고 요청을 처리하는 중요한 모듈이다.
NIoEndPoint에는 Acceptor
, Poller
, Worker
라는 요소들이 있다.
NIoEndPoint의 Acceptor는 while문을 돌면서 port listener를 통해 Socket Connection
을 얻게된다.
accept한 Socket Connection을 NioChannel object
로 캡슐화 한 다음, NioChannel객체를 PollerEvent object
로 한번 더 캡슐화한다.
PollerEvent객체는 Poller Event큐
에 저장되게 된다.
Poller Event큐에 저장된 PollerEvent객체는 Poller라는 하나의 스레드
를 통해 Poller에서 유지되는 Selector에 PollerEvent의 NioChannel을 등록한다.
출처 : https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests
Poller는 NIO의 Selector
를 가지고 있다. Selector에는 다수의 채널이 등록되어 있고, Poller는 select동작을 수행하여 데이터를 읽을 수 있는 소켓을 얻는다.
Selector에 등록된 NioChannel 중 데이터 처리를 할 수 있는 NioChannel의 소켓을 Worker Thread Pool에서 사용할수 있는 Worker 스레드
를 가져와 처리 가능한 소켓을 worker 스레드에 할당시킨다.
Poller는 NIO구현에 있어서 주요한 스레드이다. socket이 들어오면 바로 worker 스레드를 할당시켜주는 BIO Connector와 달리 Selector에 등록해두었다가 데이터 처리가 가능한 소켓을 별도로 worker 스레드에 할당시켜 줌으로써 좀 더 효율적으로 스레드를 사용 할수 있게 된다.
Worker스레드
가 Poller에 의해 소켓을 넘겨 받게 되면 socket을 SocketProcessor object
로 캡슐화한다.
그리고 Http11ConnectionHandler
에서 Http11NioProcessor객체
를 꺼내고Http11NioProcessor객체에서 CoyoteAdapter
를 호출한다.
Worker스레드 내부에서는 소켓에서 http요청을 읽고 CoyoteAdapter를 통해 HttpServletRequest
로 변경한다.
HttpServletRequest를 통해 해당 서블릿에 전달하고 소켓을 통해 응답을 다시 클라이언트에게 보낸다.
출처 : https://hadev.tistory.com/m/28
Poller와 Selector를 사용하여 각 channel을 처리하고 스레드를 할당하기 때문에 고정된 개수의 스레드로 증가하는 유저 커넥션을 핸들링 할 수 있게 되었다.
즉, 커넥션과 스레드가 1대1 매핑이 아니기 때문에 스레드 개수 이상 커넥션을 유지할수 있게 되었다.
참고로 BIO Connector는 톰캣 9.0버전 부터 삭제되었다.
https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests
https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat
https://blog.actorsfit.com/a?ID=00500-fff6744c-2f08-436f-a448-90b8a9105166
https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests