Blocking I/O
- 하나의 스레드가 I/O에 의해서 차단되어 대기하는 것
CPU 대비 많은 수의 스레드를 할당하는 멀티스레드는 문제점 발생
- 컨텍스트 스위칭으로 인한 스레드 전환 비용 발생
- 컨텍스트 스위칭
- 기존에 실행되고 있는 프로세스의 정보를 PCB라는 공간에 저장되고, 다시 실행해야 할 프로세스 정보를 PCB로부터 불러오는 과정을 컨텍스트 스위칭이라고 한다.
- 컨텍스트 스위칭이 많으면 많을수록 CPU 전체 대기 시간은 길어지기 때문에 성능은 저하된다.
- 컨텍스트 스위칭
- 과도한 메모리 사용으로 오베헤드 발생 가능성
- 새로운 스레드가 생성되면 JVM에서는 해당 스레드를 위한 스택 영역의 일부를 할당하며, 스래드 정보는 개별 프레임에 저장
- 서블릿 컨테이너 기반의 Java 웹 애플리케이션은 요청당 하나의 스레드를 할당한다. 각각 하나의 스레드 내부에서 스레드를 추가로 할당하게 된다면, 메모리 사용량 용량 높아질 가능성이 크다.
- 스레드 풀에서 응답 지연 가능성
- 톰캣에서는 스레드 풀 사용
- 대량의 요청이 발생하게 되어 스레드 풀에 사용 가능한 유후 스레드가 없을 경우, 사용 가능한 스레드가 확보되기 전까지 응답 지연이 발생한다.
Non-Blocking I/O
- Non-Blocking I/O 방식의 경우, 작업 스레드 종료 여부와 상관없이, 요청한 스레드는 차단되지 않는다.
Non-Blocking I/O의 단점
- 만약 스레드 내부에 CPU를 많이 사용하는 작업이 포함되는 경우 성능에 악영향을 미친다.
- 사용자의 요청에서 응답까지 전체 과정에서 Blocking I/O 요소가 포함된 경우에는 Non-Blocking의 이점을 누리기 힘들다.
Spring Framework에서의 Blocking I/O와 Non-Blocking I/O
- Spring MVC 기반의 웹 애플리케이션이 Blocking I/O 방식을 사용한다.
- 이러한 문제점을 극복하기 위해 나온 기술이 Spring WebFlux 이다.
- 서블릿 컨테이너 기반의 Spring MVC는 요청당 하나의 스레드를 사용하기 때문에, 대량의 요청을 처리하기 위해서 과도한 스레드를 사용하기 때문에, 오버헤드 발생
- Spring WebFlux는 Netty 같은 비동기 Non-Blocking I/O 기반의서버 엔진을 사용함으로써 적은 수의 스레드로 많은 수의 요청을 처리하기 때문에 CPU 와 메모리 를 효율적 사용 가능
Non-Blocking I/O 본사 API 서버
@Slf4j
@RestController
public class BookController {
private final RestTemplate restTemplate = new RestTemplate();
@GetMapping("/v1/books/{id}")
public BookReadResponse getBookV1(@PathVariable String id) {
String url = "<http://localhost:8081/books/>" + id + "/title";
String title = restTemplate.getForObject(url, String.class);
url = "<http://localhost:8081/books/>" + id + "/content";
String content = restTemplate.getForObject(url, String.class);
return new BookReadResponse(title, content);
}
@GetMapping("/v2/books/{id}")
public Mono<BookReadResponse> getBookV2(@PathVariable String id) {
String url = "<http://localhost:8081/books/>" + id + "/title";
Mono<String> title = WebClient.create()
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class);
url = "<http://localhost:8081/books/>" + id + "/content";
Mono<String> content = WebClient.create()
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class);
return Mono.zip(title, content)
.map(tuple -> new BookReadResponse(tuple.getT1(), tuple.getT2()));
}
}
@Slf4j
@RestController
public class BookController {
@GetMapping("/books/{id}/title")
public Mono<String> getBookTitle(@PathVariable String id) throws InterruptedException {
Thread.sleep(5000);
return Mono.just("Title " + id);
}
@GetMapping("/books/{id}/content")
public Mono<String> getBookContent(@PathVariable String id) throws InterruptedException {
Thread.sleep(5000);
return Mono.just("Content " + id);
}
}
Mono
- Mono는 Reactor에서 지원하는 Publisher 타입 중 하나로, 단 하나의 데이터만을 emit하는 Publisher 타입이다. Json은 Mono를 사용하기 적합하다.
- 본사 API를 반복적으로 호출하는 부분에서 subscribe()를 통해 응답 데이터를 전달받은 후 처리
- 본사 API로부터 전달받은 응답이 Mono타입이고, Mono는 Reactor에서 지원하는 Publisher 타입이기 때문이다.
Blocking I/O의 특징
- Non-Blocking I/O는 작업 스레드의 작업이 종료될 때까지 요청 스레드가 차단된다.
- 멀티 스레딩 기법 사용 가능, 단 사용시 컨텍스트 스위칭 전환 비용, 메모리 사용 오버헤드, 스레드 풀 응답 지연 문제 발생 가능
Non-Blocking I/O의 특징
- 작업 스레드의 종료 여부와 상관없이 요청 스레드가 차단되지 않는다.
- 적은 수의 스레드만 사용하여, 스레드 전환 비용이 적으므로 CPU 효율적 사용 가능
- CPU를 많이 사용하는 작업의 경우에는 성능에 악영향을 미칠 수 있다.
Spring Webflux를 언제 사용해야 할까
- 대량의 요청 트래픽이 발생하는 시스템
- 요청 트래픽이 충분히 감당할 수준이라면 서블릿 기반의 Blocking I/O 방식의 애플리케이션으로 충분합니다.
- 대량의 요청 트래픽이 발생한다면 Spring WebFlux 기반 애플리케이션으로 전환을 고려해 볼 만 합니다.
- 서버 증설이나 VM 확장 등을 통해 트래픽 분산이 가능하지만 그만큼 높은 비용을 지불해야 합니다.
- 마이크로서비스 기반 시스템
- 마이크로서비스 기반 시스템은 특성상 서비스들 간에 많은 수의 I/O가 지속적으로 발생합니다. 따라서 서비스들 간의 통신에서 Blocking으로 응답 지연이 발생하게 된다면 다른 서비스들에도 영향을 미칠 가능성이 높습니다.
- 스트리밍 또는 실시간 시스템
- 리액티브 프로그래밍은 HTTP 통신이나 데이터베이스 조회 같은 일회성 연결 뿐만 아니라 끊임없이 들어오는 무한한 데이터 스트림을 전달받아서 효율적으로 처리할 수 있습니다.
'📗 BOOK > 스프링으로 시작하는 리액티브 프로그래밍' 카테고리의 다른 글
4장 함수형 인터페이스 (1) | 2024.11.20 |
---|---|
2장 리액티브 스트림즈 (1) | 2024.11.20 |
1장 리액티브 시스템과 리액티브 프로그래밍 (0) | 2024.11.20 |