[SpringBoot] Controller는 어떻게 동작할까?

BlackBean99·2022년 8월 18일
1

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을 관리합니다.

동작과정

  1. Web Browser에서 Web Server 에 Http Request를 보내면 그걸 WAS Server 의 Web Server에 전달한다.
  2. WAS Server에서 Http요청을 Servlet Container에 전달한다.
  3. 힙 메모리에 요청처리에 필요한 Servlet Instance가 있는지 확인하고 없으면, Servlet Instance를 생성하고 초기화한다. init()
  4. Servlet Instance 의 service() 메소드를 호출. Http 요청처리 후 결과를 WAS Server의 Web Server에게 처리 결과를 전달한다.
  5. WAS Server의 Web Server는 Http 응답을 Web Server에게 전달하고 이를 받은 Web Server는 Web Browser에 전달.

ServletContainer 내부 동작과정

  1. HttpRequest가 ServletContainer에 들어오면 HttpServletRequest, HttpServletResponse 두 객체를 생성.
    1. GET/POST 여부에 따라 doGet(), doPost()를 실행한다.
  2. 동적 페이지 생성 후 HttpServletRersponse객체에 응답을 보내고, Servlet Class는 JVM의 Class Loader에 의해 로딩된다. ( Servlet 생성자 호출 ) → 매개변수가 없어서 Thread-Safe하지 않아 여러개의 요청을 동시에 처리할 수 있다.
  3. ServletContainer는 사용되지 않아 제거되어야 할 Servlet Instance는 destroy()함수를 호출하고 JVMGC(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하거나 쓰레드를 할당.

질문!

  1. Apache 웹 서버 하나에 여러개의 Tomcat Instance를 띄우면?
  • 이는 JVM을 여러개를 동시에 돌리겠다는 이야기다.하지만 각각의 Tomcat Instance는 독립적이기 때문에 영향을 받지 않지만, 가상 메모리 스와핑 오버헤드가 발생한다.
  1. 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들을 제어할 수 있다.

  1. WepApplication이 실행되면 AS에 의해 web.xml이 로딩됨
  2. web.xml에 등록된 ContextLoaderListener에 의해 ApplicationContext(Bean이 등록되는 곳 )가 생성된다.
    1. ServletContext가 Bean을 접근하려면 ApplicationContext를 참조해야한다.
  3. 생성된 ApplicationContext에 등록된 Container가 구동되며 개발자 작성한 DAO,DTO,VO등 객체가 생성된다.
  4. Spring의 DispatcherServlet(Front Controller패턴을 구현한 것)이 생성된다. Request가 들어오면 Dispatcher Servlet으로 들어 올 수 있도록 web.xml에 등록해놓는다.
  5. HandlerMapping에서 찾은 Handler(Controller)의 메서드를 호출하고, 이를 ModelAndView 형태로 바꿔준다.

결국 SpringMVC도 ServletContainer가 관리하고 있는 Servlet중 한개다.

이런식으로 개발자가 아닌 프로그램에 의해 객체들이 관리되는 것을 IoC(Inversion of Control)이라고 한다.


SpringBoot동작과정

  1. DispatcherServlet 이 스프링에 @Bean으로 등록됨.
  2. DispatcherServlet 컨텍스트에 서블릿을 등록한다.
  3. Servlet Container Filter에 등록 설정 해놓은 Filter들을 등록한다.
  4. 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는 수만건 요청을 받아도 문제가 생기지 않는다.
profile
like_learning

0개의 댓글