스프링을 공부하다 보니, 하나의 컨트롤러가 들어오는 수 많은 요청들을 처리해준다는 것을 그저 읽기만 했다. 곰곰히 생각하니, 떠오르는 의문들이 많고 내가 명확히 알고 있지 않다는 생각이 들어, 포스트로 정리하게 되었다.
- 하나의 컨트롤러가 아니라면, 각각의 요청에 대해 하나하나 컨트롤러가 따로 작용하는걸까 ?
- 한 컨트롤러가 처리한다면, 그럼 컨트롤러가 1번 요청을 처리할때에는, 나머지들은 놀고있을까 ?
- 한 컨트롤러가 처리한다면, 수많은 요청들 사이에서 어떻게 정확하게 값을 전달해줄 수 있을까 ?
이렇게 세가지의 의문점이 생겼고, 이 의문점에 대해 하나씩 접근해가면서 저 질문에 대한 답을 찾아나가려고 했다.
(1) 더모티 프로젝트의 컨트롤러 코드
↑ 더모티 컨트롤러 코드, 빈으로 하나 등록해줬다.
직접 작성했었던 코드를 보면, 컨트롤러 빈으로 등록해줬던 것은 하나였다. 티모태에선 post 와 reply 컨트롤러가 각각 생성되어있었지만, 하나의 컨텍스트 내에서는 사용되는 컨트롤러는 하나였기에, 하나의 객체가 생성되어 사용되고 있었음.
(2) 컨트롤러 객체 실험 내용
↑ 한 사이트 에서 하나의 컨트롤러 객체에 대한 실험 내용. 하나의 컨트롤러에 대해 여러 request를 보낸 것에 대한 결과.
하나의 컨트롤러에 대해서 여러 request를 보내도, 같은 컨트롤러 객체가 처리를 해준 모습 !
=> 이 모든것은 스프링의 싱글턴 덕분이다.
결과 : 싱글턴 덕분에 객체는 하나가 생성되는 것이 맞다 !
객체는 하나가 생성되니, 하나의 객체를 어떻게 공유하는지 의문이 생김.
공유하지 않는다면 다른 요청은 미리 온 요청이 처리될 때 까지 기다려야 한다고 생각했고, 그러면 병목현상이나 비동기 등 여러 문제가 발생할 것이라고 생각했다.
-> 하지만 문제는 발생하지 않는다 !
(1) JVM 이 실행할 수 있도록 java 파일을 컴파일
(2) 컴파일된 class 파일을 class loader 가 받아감.
(3) 이후 런타임 데이터 영역에 올려짐.
(4) 실행엔진이 컴파일을 한 다음 실행을 하게됨. (이떄 컴파일 하는 방식은 인터프리터와 jit 컴파일러 방식이 있다.)
(+) 런타임 영역에는 힙, 메소드 영역, 스택, pc 레지스터, 네이티브 메소드 스택 으로 나뉜다.
- 메소드 영역: 클래스 변수의 이름, 데이터 타입, 접근 제어자, constant pool, static, final, 리턴 타입, 파라미터 등이 생성되는 영역
- 힙 영역: new 를통해 생성된 객체와 배열들이 생성되는 영역이고, 가비지 컬렉터의 대상이 되는 영역
- 스택 영역: 지역 변수, 리턴 값, 연산에 사용되는 임시 값 들이 생성되는 영역
- pc 레지스터: 쓰레드가 생성될때마다 생성되는 영역으로, 스레드의 주소와 영역을 저장하기 때문에 스레드를 돌아가면서 수행할 수 있도록 해준다.
- 네이티브 메소드 스택: 자바 외 언어로 작성된 코드를 위한 영역이다.
컨트롤러의 객체는 heap, 컨트롤러의 처리 로직이나 메소드들은 Method Area 에 저장된다.
이것을 잘 기억하고 있자.
- 스레드는 힙과 데이터 영역을 공유한다. => 상태를 가지지 않도록 개발하는 것이 중요 !
- 그리고 각각 고유한 스택 영역을 갖는다.
- 위의 메모리 구조를 생각할 때, heap 과 method area를 공유하고, stack 과 같은 영역은 독자적인 영역을 가지게 된다.
- 그리고 스프링 부트는 스레드를 이용해서 요청을 처리한다.
- 스프링부트는 TOMCAT을 사용한다.
- TOMCAT은 부팅 시 스레드 컬렉션인 thread pool 을 생성한다.
- 유저 요청이 들어온다면, 스레드 풀에서 해당 요청에 맞게 스레드를 할당하여 처리를 한다.
- 처리 완료 후 스레드 풀로 반환해준다. (이후 계속 반복)
즉, 스프링부트는 스레드를 이용해서 요청을 처리해주기 때문에, 한번에 많은 요청이 와도, 싱글톤이 적용된 컨트롤러 객체를 사용하면 객체를 모든 요청마다 생성해주지 않아도 처리가 가능했던 것을 알 수 있다.
따라서 서로 다른 요청들은 끝나길 기다릴 필요가 없이 동시에 접근해서 처리가 가능하고, 스레드의 특성으로 값이 잘못된 값으로 참조되지 않고 처리가 가능한 것이다.
스레드 + 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