[SPRING] 하나의 컨트롤러가 어떻게 수많은 request를 처리할 수 있을까 ?

gnoesnooj·2022년 5월 1일
0

들어가기

스프링을 공부하다 보니, 하나의 컨트롤러가 들어오는 수 많은 요청들을 처리해준다는 것을 그저 읽기만 했다. 곰곰히 생각하니, 떠오르는 의문들이 많고 내가 명확히 알고 있지 않다는 생각이 들어, 포스트로 정리하게 되었다.

처음 보고 들은 생각..

  1. 하나의 컨트롤러가 아니라면, 각각의 요청에 대해 하나하나 컨트롤러가 따로 작용하는걸까 ?
  1. 한 컨트롤러가 처리한다면, 그럼 컨트롤러가 1번 요청을 처리할때에는, 나머지들은 놀고있을까 ?
  1. 한 컨트롤러가 처리한다면, 수많은 요청들 사이에서 어떻게 정확하게 값을 전달해줄 수 있을까 ?

이렇게 세가지의 의문점이 생겼고, 이 의문점에 대해 하나씩 접근해가면서 저 질문에 대한 답을 찾아나가려고 했다.


첫번째 접근 - 컨트롤러 객체가 하나 생성되는게 맞을까 ? - 싱글턴

(1) 더모티 프로젝트의 컨트롤러 코드

↑ 더모티 컨트롤러 코드, 빈으로 하나 등록해줬다.
직접 작성했었던 코드를 보면, 컨트롤러 빈으로 등록해줬던 것은 하나였다. 티모태에선 post 와 reply 컨트롤러가 각각 생성되어있었지만, 하나의 컨텍스트 내에서는 사용되는 컨트롤러는 하나였기에, 하나의 객체가 생성되어 사용되고 있었음.

(2) 컨트롤러 객체 실험 내용


↑ 한 사이트 에서 하나의 컨트롤러 객체에 대한 실험 내용. 하나의 컨트롤러에 대해 여러 request를 보낸 것에 대한 결과.
하나의 컨트롤러에 대해서 여러 request를 보내도, 같은 컨트롤러 객체가 처리를 해준 모습 !
=> 이 모든것은 스프링의 싱글턴 덕분이다.

결과 : 싱글턴 덕분에 객체는 하나가 생성되는 것이 맞다 !


두번째 접근 - JVM 구조

객체는 하나가 생성되니, 하나의 객체를 어떻게 공유하는지 의문이 생김.
공유하지 않는다면 다른 요청은 미리 온 요청이 처리될 때 까지 기다려야 한다고 생각했고, 그러면 병목현상이나 비동기 등 여러 문제가 발생할 것이라고 생각했다.
-> 하지만 문제는 발생하지 않는다 !

간단한 JVM 동작 원리

(1) JVM 이 실행할 수 있도록 java 파일을 컴파일
(2) 컴파일된 class 파일을 class loader 가 받아감.
(3) 이후 런타임 데이터 영역에 올려짐.
(4) 실행엔진이 컴파일을 한 다음 실행을 하게됨. (이떄 컴파일 하는 방식은 인터프리터와 jit 컴파일러 방식이 있다.)

(+) 런타임 영역에는 힙, 메소드 영역, 스택, pc 레지스터, 네이티브 메소드 스택 으로 나뉜다.

  • 메소드 영역: 클래스 변수의 이름, 데이터 타입, 접근 제어자, constant pool, static, final, 리턴 타입, 파라미터 등이 생성되는 영역
  • 힙 영역: new 를통해 생성된 객체와 배열들이 생성되는 영역이고, 가비지 컬렉터의 대상이 되는 영역
  • 스택 영역: 지역 변수, 리턴 값, 연산에 사용되는 임시 값 들이 생성되는 영역
  • pc 레지스터: 쓰레드가 생성될때마다 생성되는 영역으로, 스레드의 주소와 영역을 저장하기 때문에 스레드를 돌아가면서 수행할 수 있도록 해준다.
  • 네이티브 메소드 스택: 자바 외 언어로 작성된 코드를 위한 영역이다.

컨트롤러의 객체는 heap, 컨트롤러의 처리 로직이나 메소드들은 Method Area 에 저장된다.

이것을 잘 기억하고 있자.

스레드

  • 스레드는 힙과 데이터 영역을 공유한다. => 상태를 가지지 않도록 개발하는 것이 중요 !
  • 그리고 각각 고유한 스택 영역을 갖는다.
  • 위의 메모리 구조를 생각할 때, heap 과 method area를 공유하고, stack 과 같은 영역은 독자적인 영역을 가지게 된다.
  • 그리고 스프링 부트는 스레드를 이용해서 요청을 처리한다.

세번째 접근 - 스프링 부트의 요청 처리 방법

  1. 스프링부트는 TOMCAT을 사용한다.
  2. TOMCAT은 부팅 시 스레드 컬렉션인 thread pool 을 생성한다.
  3. 유저 요청이 들어온다면, 스레드 풀에서 해당 요청에 맞게 스레드를 할당하여 처리를 한다.
  4. 처리 완료 후 스레드 풀로 반환해준다. (이후 계속 반복)

즉, 스프링부트는 스레드를 이용해서 요청을 처리해주기 때문에, 한번에 많은 요청이 와도, 싱글톤이 적용된 컨트롤러 객체를 사용하면 객체를 모든 요청마다 생성해주지 않아도 처리가 가능했던 것을 알 수 있다.
따라서 서로 다른 요청들은 끝나길 기다릴 필요가 없이 동시에 접근해서 처리가 가능하고, 스레드의 특성으로 값이 잘못된 값으로 참조되지 않고 처리가 가능한 것이다.


정리

스레드 + JVM + 싱글턴 (+ 상태를 가지지 않도록 하는 개발 !)
=> 하나의 컨트롤러가 수많은 요청을 처리해주는 것이 가능하다 !

추가)
(하나의 컨트롤러가 처리를 해준다 라는 접근보단, 여러 스레드들이 컨트롤러 객체에 접근해서 필요한 로직들을 사용하는 개념으로 접근하는 것이 좋다는 글도 보게 되었다. 이런 접근이 더 적절하다고 생각한다.)

참조

JAVA컴파일 과정 : https://yang-droid.tistory.com/48
controller 객체는 하나일까 ? 에 대한 답을 준 곳 : https://darkstart.tistory.com/255
스레드 풀 : https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests#nio-connector

profile
누구나 믿을 수 있는 개발자가 되자 !

0개의 댓글