Spring ,Servlet 차이
Spring Bean이 Singleton Pattern이고, thread-safe하지 않으므로 stateless하게 설계해야 한다
Controller 1개가 어떻게 수십만의 요청을 처리하는가?
WAS로 Tomcat 을 많이 쓰는데 worker thread 가 200개이다.
Tomcat은 한개의 프로세스에서 동작하고 Thread pool 을 만들어 HttpRequest가 들어올때, 하나씩 Thread를 재사용 및 재배정을 진행.
Request별로 Thread가 별도로 생성되고 이에 각각의 ServletContext를 갖는다.
하지만 수많은 쓰레드가 어떻게 1개의 Controller 객체를 공유할까?
Controller 객체하나를 생성하면 객체 자체는 Heap 에 생성하지만 Class의 정보는 Method Area(Permanent Area)에 저장된다.
모든 쓰레드가 객체의 binary Code 정보를 공유할 수 있다는 뜻이다.
굳이 Controller 를 사용하고 있는 쓰레드나 Controller 가 Block하지 않아도 된다. 그냥 내부 상태가 없으니 그냥 메소드 호출만 하면 된다는 뜻이다. 로직만 공유되기 때문이다.
만약에 Bean 이 상태를 가진다면, scope = prototype이라면 bean 에 cache를 모두 사용하고 부족할 것이다.
그래서 SpringBean을 생성시 Stateful이 아닌, Stateless하게 만들어야 한다.
Servlet Container vs Spring Container 차이
Servlet 은 Java EE 의 표준으로 Server 에서 동작하는 Class 들을 의미한다. 각 Servlet 은 init(), service(), destroy() 3개의 method를 정의해야한다.
- init() : Servlet 생성시 호출 - > Parameter로 ServletConfig Interface기반의 Instance가 넘어오는데, Servlet을 초기화하고 자원을 할당.
- service() : Servlet으로 요청이 전달될 때마다 호출. 실제 Logic을 실행
- destroy() : Servlet이 삭제될때 자원을 해지
웹 서버의 측면에서 서블릿은 Socket의 생성, Input/OutputStream의 생성을 대신 진행해준다.
Container는 Servlet의 life-cycle을 관리, 매번 요청이 올 때마다 새 쓰레드를 요청별로 부여. → Container는 여러 thread를 관리한다.
즉 Client기준이 아니라 각각의 요청에만 새 Thread를 만들어 낸다.
Client 기준으로 즉 Thread-per-connection을 우선으로 쓰지 않는 것은
Scalability를 가져가기 위함이다. Java 쓰레드는 1Mb메모리를 segment가 하나씩 붙는다. active 상태든, idle 상태든 상관없이 붙인다. 그래서 thread-per-request를 써야 request를 사용할 때만 사용하면 경제적입니다.
굳이 필요하다면 지속적으로 요청을 날려야 한다면 HTTP Keep-alives를 사용하여 유지할 수 있다.
ServletContainer
Servlet Container(Web Container)는 Servlet Instance의 Life-cycle을 관리합니다.
동작과정
- Web Browser에서 Web Server 에 Http Request를 보내면 그걸 WAS Server 의 Web Server에 전달한다.
- WAS Server에서 Http요청을 Servlet Container에 전달한다.
- 힙 메모리에 요청처리에 필요한 Servlet Instance가 있는지 확인하고 없으면, Servlet Instance를 생성하고 초기화한다. init()
- Servlet Instance 의 service() 메소드를 호출. Http 요청처리 후 결과를 WAS Server의 Web Server에게 처리 결과를 전달한다.
- WAS Server의 Web Server는 Http 응답을 Web Server에게 전달하고 이를 받은 Web Server는 Web Browser에 전달.
ServletContainer 내부 동작과정
- HttpRequest가 ServletContainer에 들어오면 HttpServletRequest, HttpServletResponse 두 객체를 생성.
- GET/POST 여부에 따라 doGet(), doPost()를 실행한다.
- 동적 페이지 생성 후 HttpServletRersponse객체에 응답을 보내고, Servlet Class는 JVM의 Class Loader에 의해 로딩된다. ( Servlet 생성자 호출 ) → 매개변수가 없어서 Thread-Safe하지 않아 여러개의 요청을 동시에 처리할 수 있다.
- ServletContainer는 사용되지 않아 제거되어야 할 Servlet Instance는 destroy()함수를 호출하고 JVM에 GC(Garbage Collector)에서 해지할 수 있도록 체크해둔다. GC는 표시된 Servlet Instance를 해지한다.
이런 ServletContainer는 우리가 잘 들어본 Tomcat이다.
Servlet Container는 하나의 WebApplication 하나씩 붙고, JVM도 한개씩 붙는다. ( One WAS per One JVM )
Application을 시작하면 Servlet Listener는 GenericServlet 추상클래스가 두 인터페이스의 추상메서드를 구현하고(상속받고) doPost, doGet 메소드를 구현한다.
Apache와 Tomcat의 차이
Tomcat
- 멀티쓰레드 방식
- 요청받으면, Tomcat Engine이 요청에 맞는 Context를 찾고, Servlet에 전달 처리.
- JVM위에서 실행됨. (JVM 1개)
Tomcat이 Request 처리하는 과정
- 쓰레드 풀을 관리하는 Acceptor Thread 하나가 존재하여, 이를 관리하는 여러개의 Thread를 동시에 띄어둠.
- 요청이 들어오면 available worker thread와 connection을 맺어줌.
- worker Thread는 Tomcat engine에 request를 보내고, request header와 associated virtual host and contexts에 따른 적합한 응답을 보내달라고 요청함.
- 이 후 Tomcat은 client와의 Socket 통신이 열리면 worker thread를 활성화
Apache
- 멀티 프로세스 방식
- 항상 idle한 수의 프로세스 및 쓰레드를 생성해둠.
- 요청이 들어올때 기다리지 않고 바로 매칭해줌.
- Apache MPM 방식에 따라 프로세스를 fork하거나 쓰레드를 할당.
질문!
- Apache 웹 서버 하나에 여러개의 Tomcat Instance를 띄우면?
- 이는 JVM을 여러개를 동시에 돌리겠다는 이야기다.하지만 각각의 Tomcat Instance는 독립적이기 때문에 영향을 받지 않지만, 가상 메모리 스와핑 오버헤드가 발생한다.
- JVM이 Servlet을 실행하는 것과 일반 Java Class를 실행하는 것의 차이는?
- Servlet은 main함수로 호출되지 않는다는 차이. Servlet의 호출 주체는 Tomcat과 같은 Web Container 에 의해 실행되는 것이다. Container가 web.xml을 읽고 서블릿 클래스를 class loader에 등록.
SpringContainer
앞서 하루종이 ServletContainer를 설명한 이유가 있다.
Spring Container 는 Bean LifeCycle을 관리한다.
Spring Container에는 BeanFactory가 있고 ApplicationContext는 이들을 상속하여 두개의 컨테이너로 DI된 Bean들을 제어할 수 있다.
- WepApplication이 실행되면 AS에 의해 web.xml이 로딩됨
- web.xml에 등록된 ContextLoaderListener에 의해 ApplicationContext(Bean이 등록되는 곳 )가 생성된다.
- ServletContext가 Bean을 접근하려면 ApplicationContext를 참조해야한다.
- 생성된 ApplicationContext에 등록된 Container가 구동되며 개발자 작성한 DAO,DTO,VO등 객체가 생성된다.
- Spring의 DispatcherServlet(Front Controller패턴을 구현한 것)이 생성된다. Request가 들어오면 Dispatcher Servlet으로 들어 올 수 있도록 web.xml에 등록해놓는다.
- HandlerMapping에서 찾은 Handler(Controller)의 메서드를 호출하고, 이를 ModelAndView 형태로 바꿔준다.
결국 SpringMVC도 ServletContainer가 관리하고 있는 Servlet중 한개다.
이런식으로 개발자가 아닌 프로그램에 의해 객체들이 관리되는 것을 IoC(Inversion of Control)이라고 한다.
SpringBoot동작과정
- DispatcherServlet 이 스프링에 @Bean으로 등록됨.
- DispatcherServlet 컨텍스트에 서블릿을 등록한다.
- Servlet Container Filter에 등록 설정 해놓은 Filter들을 등록한다.
- DispatcherServlet에 각종 핸들러 Mapping(URL)들이 등록된다.(컨트롤러들이 다 생성되어 싱글톤으로 관리된다.)
- 요청이 오면 Servlet처럼 바로 객체를 생성하는 것이 아니라 @Controller가 @Bean Singleton으로 등록되어있는 것이다.
- DispatcherServlet(WebApplicationContext사용)은 ContextLoaderListener(ApplicationContext, root-context)를 상속하여 사용한다.
이 말은 Data Repository나 비즈니스 서비스 같은 공통 자원은 root에 있다는 것이다.
Servlet은 Spring Container를 어떻게 look-up 하는가?
SpringContainer는 Bean Initializing작업을 거친다. Bean은 BeanDefinition 객체로 정의 후, 객체를 생성한다. 이후 Java Reflection을 통해 객체를 생성한다.
생성 시, Service-Locator 패턴 으로 의존성을 주입하고 생성한다.
이 패턴은 cache라는 map 객체에서 내가 원하는 객체를 찾은 결과를 보관하여 저장한다. 메모리에서 찾으면 DB에서 조회해오는 것보다 큰 성능향상을 갖는다.(이를 영속성 Context라고 한다.)
외부 컨테이너에서 생성된 객체를 주입해서 결합도를 낮추는 효과가 있다.
이 주입은 https://taes-k.github.io/2021/05/23/spring-di-reflection/ 를 참고!
요약
- singleton으로 관리되는 Bean을 생성하기 위해서는
(ComponentScan → Bean initialize → getBean)
- Servlet은 stateless, immutable하게 구현했기 때문에 동기화 과정이 필요하지 않아 ServletContainer가 관리하는 Servlet중 하나인 Spring의 Controller는 수만건 요청을 받아도 문제가 생기지 않는다.