SpringMVC - Tomcat는 어떻게 동작하나?
- SpringMVC에서 많이 사용하는 WAS는 Tomcat → 쓰레드 풀의 개수가 200개 이상
- 동작 방법
- 요청이 들어오면 ThreadPool에서 Thread를 하나 사용
- 그러나 I/O가 발생하면 CPU를 block 시킴
- 이 때 다른 요청이 들어오면 ThreadPool에서 Thread를 하나 사용
- 이런 식으로 쓰레드를 돌아가면서 요청 처리하고, block이 풀리면 작업을 이어나감
- 결론
- Thread가 엄청 많이 필요하다
- 쓰레드가 많으면 쓰레드 사이에서 공유 자원의 동기화 이슈가 생긴다
- Lock 하고 있는 자원에 접근하려는 쓰레드는 기다려야 해서 처리가 느리다
💡 컨텍스트 스위칭: 실행중인 프로세스가 변경이 되면 CPU내 레지스터의 값이 변경되어야 하는데, 변경 되기 전에 이전 프로세스가 지니고 있던 데이터들을 어딘가에 저장해주어야 한다. 그러다 다시 이전 프로세스를 이어가려면 값을 불러와야한다. 이 과정에서 시스템에 부담을 많이 준다.
스레드는 공유하는 메모리 영역이 많기 때문에 프로세스보다 비교적 컨텍스트 스위칭이 빠르긴 하지만, 그래도 여전히 부담을 준다.
WebFlux는 어떻게 적은 쓰레드 만으로 많은 요청을 처리할 수 있을까?
- 적은 쓰레드로 처리 (core*2)
- 쓰레드 구성
- 요청을 받는 쓰레드(A)
- block 상태에서 풀린 쓰레드의 요청을 처리하는 쓰레드(B)
- block 상태가 풀렸는지 무한 루프 돎녀서 감시하는 event loop를 위한 쓰레드(C)
- 동작 방법
- 요청이 들어오면 A 쓰레드에서 처리
- 그러다 I/O 발생시 CPU block시킴
- 이런 비동기 작업을 처리하기 위해 Queue에 넣음
- A쓰레드는 계속해서 요청을 받아 처리
- C는 Queue를 무한뤁프 돌면서 감시
- C에서 감시를 하다가 작업이 끝난 이벤트가 있으면 B 쓰레드에서 해당 이벤트를 처리
- 결론
- MQ처럼 이벤트 기반으로 비동기 처리하는 개념인 것 같다
- 쓰레드가 적어 공유 자원의 동기화 이슈가 적게 발생해 더 빠르다
NodeJS에서는 어떻게 싱글 쓰레드로 동작할까?
- NodeJS도 비동기 처리
- 구성
- 이벤트 루프: 이벤트 발생시 콜 스택을 확인하고 콜 스택이 비어있는 경우에만 테스크 큐에 콜백 함수를 넘겨줌
- 테스크 큐: web api에서 비동기 작업들이 실행된 후 호출되는 콜백 함수들이 기다리는 공간. FIFO 처리
- web api: 브라우저에서 지원하는 api로 DOM이벤트 Ajax 등 비동기 작업을 수행할 수 있도록 지원
- 동작 방법
- 이벤트 발생
- 콜 스택에 쌓이게 됨
- web api가 비동기 작업 수행
- 콜백 함수를 이벤트 루프를 통해 테스크 큐에 넘겨주게 됨
- 이벤트 루브는 콜 스택에 쌓여있는 함수가 없을 때 테스크 큐에서 대기하고 있던 콜백 함수를 콜 스택에 넘겨줌
- 콜 스택에 쌓인 콜백함수가 실행된 후 스택에서 제거됨
- 결론
- 싱글 스레드지만 비동기 I/O 작업을 통해 요청들을 서로 블로킹 하지 않음
- 동시에 많은 요청을 비동기로 수행함으로 써 싱글스레드 일지라도 논 블로킹 가능
- 완전한 싱글 스레드는 아니다
- NodeJS는 싱글 스레드이지만 완전한 싱글스레드를 기반으로 동작하지는 않는다
- 일부 블로킹 작업들은 libuv 스레드 풀에서 수행됨
- 자세한 내용은 하단의 링크 참고
NginX
- 개념
- WAS 중 하나
- Event-Driven한 방식으로 한개의 고정된 프로세스만 생성
- 요청에 대한 처리는 스레드에 의존하지 않고 프로세스 내부에서 비동기 방식으로 처리
- 동시 접속 요청이 많아도 비동기 처리로 인해 스레드 생성 비용 따로 들지 않는다
- 문제점
- Event 처리 기간이 길어져 단일 Thread를 점유하고 있으면 다른 Event처리도 느려지는 Blocking 현상
- 해결
- NginX Thread Pool 도입 (1.7.11 버전 이후)
- worker precess가 긴 작업인 경우 직접 실행하지 않고 task queue 작업에 넘김
- task queue는 thread pool에서 작업을 수행할 수 있는 스레드에게 작업을 주면서 풀 관리
- MultiThread 가능하면서, 하나의 thread가 blocking 되어도 나머지 thread가 작동하는데 문제 없다.
내 의견
- 동기화 이슈를 좀 덜 겪으려면 스레드가 적을수록 좋은 것 같고, 스레드 생성 비용도 크기 때문에 점점 적은 쓰레드로 경량화 하는게 트렌드 인가보다
- 이를 위해서는 task를 queue에 쌓아서 해결하는 비동기 + Event-driven 방식을 주로 도입하는 추세인 듯 하다