티스토리 뷰
스프링 부트에 내장되어있는 서블릿 컨테이너(Tomcat)에서 다중요청을 처리한다.
스프링과 스프링부트의 주요한 차이점 중 하나는, 스프링 부트에서 내장 서블릿 컨테이너(Tomcat)를 지원한다는 것이다.
- 톰캣은 다중 요청을 처리하기 위해서 부팅할 때, 스레드풀을 생성함
- 모든 요청에 대해 스레드를 생성하고 소멸하는 것은 OS이나 JVM에 대해 많은 부담을 안겨줄 수 있고, 동시에 일정 이상의 다수 요청이 들어올 경우 리소스(CPU와 메모리 자원) 소모에 대한 억제가 어렵다! - HttpServletRequest가 들어오면, 스래드풀에서 하나씩 스레드를 할당
- 해당 스레드에서 스프링부트에서 작성한 Dispatcher Servlet을 거쳐 유저의 요청을 처리함 - 작업을 모두 수행하고 나면 스레드는 스레드풀로 반환됨
→ 첫 작업이 들어오면 코어 사이즈만큼의 스레드를 생성하고 → 유저 요청(커넥션이나 소켓에 accept한 객체)이 들어올 때마다 작업 큐에 담음 → 코어 사이즈의 스레드 중 유휴상태인 스레드가 있다면 작업 큐에서 작업을 꺼내서 스레드에 작업을 할당하여 작업을 처리 → 만약에 유휴 상태인 스레드가 없으면 작업은 큐에서 대기
그런데.. 스프링 빈은 대부분 싱글톤 패턴으로 생성되어 Application Context에 의해 관리된다. 즉 모든 컨트롤러, 서비스, 레퍼지토리는 하나의 인스턴스만 가지고 있다는 것이다.
Q. 스프링 환경은 멀티스레드인데 어떻게 Thread-Safe를 유지할 수 있을까?
A. Thread-Safe하지 않다. 단지, Thread-Safe하도록 코드를 짤 뿐..! 모든 스레드는 스프링 빈을 공유하고 멤버 변수도 공유한다. 우리가 보통 사용하는 컨트롤러나 서비스 같은 빈에서 사용하는 객체들은 주입을 받아서 사용하고 내부적으로 가진 멤버 변수를 변화시키지 않는다.
- Controller 객체 하나를 생성하면,
- 객체 자체는 → Heap에 생성되지만,
- 해당 Class의 정보는 → Method Area(또는 Permanent Area)에 저장된다.
- 결국 Heap영역이던 Method Area이던 모든 스레드가 객체의 Binary Code 정보를 공유할 수 있다는 뜻이다. 공유되는 정보를 사용하기 위하여 굳이 Controller 객체 자체가 Block될 필요는 없다는 것이다.
- Controller가 내부적으로 상태를 갖는 것이 없으니, 그냥 메소드 호출만 하면 되기 때문에 굳이 동기화할 이유도 없고 명분도 없고 그저 처리 로직만 ‘공유되어’ 사용되는 것이기 때문에 몇 십만 개의 요청이 들어오든 상관없다.
아까부터 계속 언급되는 서블릿과 서블릿 컨테이너란 무엇일까?
→ 서블릿은 java를 사용하여 클라이언트의 요청에 대해 동적으로 웹페이지(html)을 생성해서 응답해주는 웹 어플리케이션 컴포넌트이다. Tomcat 같은 서블릿 컨테이너에 의해 실행된다.
서블릿 생성 주기
- init() : 서블릿 생성 시 한번만 호출 → 서블릿을 초기화하고 서블릿이 이용하는 자원을 할당하는 동작을 수행
- sevice() : 서블릿으로 요청이 전달될 때마다 호출 → 실제 서비스 로직을 수행
- destroy() : 서블릿 삭제 시 호출 → 서블릿에서 이용하는 자원을 해지하는 동작을 수행
서블릿 컨테이너는 사용되지 않아 제거되야할 서블릿 인스턴스의 destory() method를 호출하고 JVM의 GC(Garbage Collector)에서 서블릿 인스턴스를 해지할 수 있도록 표시해둔다. GC는 표시된 서블릿 인스턴스를 해지한다.
서블릿 동작과정
- 사용자가 URL을 클릭
- http requset를 서블릿 컨테이너(ex. 톰캣)에 보낸다
- 서블릿 컨테이너는 HttpServletRequest, HttpServletResponse 두 객체를 생성
- 사용자가 요청한 URL을 분석하여 어느 서블릿의 요청인지 찾고
- 해당 서블릿의 sevice() 메소드를 호출하여 POST/GET 여부에 따라 doGet()/doPost() 호출
- 걔가 동적인 페이지를 생성한 후 HttpServletResponse 객체에 응답을 보냄
- HttpServletRequest, HttpServletResponse 소멸!
서블릿 인스턴스는 HTTP 요청이 올 때마다 기존의 서블릿 인스턴스를 이용한다. 즉, 하나의 서블릿 인스턴스가 여러개의 HTTP 요청을 동시에 처리하게 된다. 따라서 서블릿 인스턴스는 Thread-Safe하지 않다.
(Thread-Safe한 변수를 이용하기 위해서는 Method의 지역변수를 이용해야 한다)
서블릿 컨테이너
- 웹 서버와의 통신을 지원 : 서블릿과 웹 서버가 쉽게 통신할 수 있도록 해줌. 소켓기능들을 API로 제공하여 복잡한 과정을 생략하고 개발자가 비즈니스 로직에 대해서만 초점을 맞출 수 있도록 (소켓의 생성, I/O 스트림의 생성 등)
- 서블릿 생명 주기 관리 : 서블릿의 탄생과 죽음을 관리함. 서블릿 클래스를 로딩해서 인스턴스화 하고, 초기화 메소드를 호출해주며, 요청이 들어오면 적절한 서블릿 메소드를 호출한다
- 멀티스레드 지원 및 관리 : 서블릿 컨테이너는 요청이 올 때 마다 새로운 자바 스레드를 하나 매칭 (Thread-per-request)
- 선언적인 보안 관리 : 보안 관리를 XML 배포 서술자에 기록하므로 보안적 이슈로 자바 소스를 수정하지 않아도 되게끔 함
우리가 대표적으로 알고 있는 서블릿 컨테이너는 Tomcat이다. 서블릿 컨테이너는 하나의 웹어플리케이션에 하나씩 붙는다 → one WAS per one JVM
(+) 스프링 컨테이너의 생성 과정
1. WebApplication이 실행되면, WAS(Tomcat, ServletContainer per process)에 의해 web.xml이 로딩된다
2. web.xml에 등록되어 있는 ContextLoaderListener(ServletContextListener 인터페이스를 구현한 것)가 Java Class 파일로 생성된다
3. ContextLoaderListener가 ApplicationContext.xml에 따라 ApplicationContext를 생성한다
→ ApplicationContext도 서블릿 컨테이너에 단 한 번만 초기화되는 서블릿이다
4. ApplicationContext.xml에 등록되어 있는 설정에 따라 스프링 컨테이너가 구동된다
(이 때 개발자가 작성한 비즈니스 로직과 DAO 등의 객체가 생성된다)
5. Client로부터 Web Application 요청이 왔다! → 스프링의 DispatcherServlet도 서블릿이니 이 때 딱 한 번만 생성된다
→ DispatcherServlet은 Front Controller 패턴을 구현한 것
6. 처음에 Request가 들어오면, DispatcherServlet으로 간다
→ DispatcherServlet도 서블릿이기 때문에 web.xml에 등록이 되어 있다. 모든 요청이 오면 DispatcherServlet로 가라고 하고 등록을 시켜 놓는다.
7. 그에 맞는 요청에 따라 적절한 Controller를 찾는다
결국 개발자가 작성한 비즈니스 로직도 서블릿 컨테이너가 관리하게 되고, 스프링 컨테이너도 서블릿 컨테이너가 관리하고 있는 서블릿 한 개를 의미한다.
참고 링크
https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests
https://doflamingo.tistory.com/44
https://velog.io/@jakeseo_me/자바-서블릿에-대해-알아보자.-근데-톰캣과-스프링을-살짝-곁들인
https://jypthemiracle.medium.com/servletcontainer와-springcontainer는-무엇이-다른가-626d27a80fe5
'Spring' 카테고리의 다른 글
JUnit 4와 5 비교 (0) | 2022.05.16 |
---|---|
JPA 영속성 컨텍스트 (0) | 2022.04.28 |
Redisson 분산락 (0) | 2022.04.27 |
컴포넌트 스캔과 수동 Bean 등록 (0) | 2022.04.24 |
OSIV (0) | 2022.04.24 |