728x90
2024.06.10 - [CS/OS] - 동기 VS 비동기, 블로킹 VS 논블로킹
- 본사에서 클라이언트로부터 도서 정보 조회 요청을 받으면 지점으로 도서 정보 조회 API 요청 보내는 예시코드
- (클라이언트 ↔️ 본사 ↔️ 지점)
Spring MVC
- Blocking 방식: 스레드가 차단됨
- 다섯개의 책을 조회하는 API호출 지연시간 : 약 25초 정도(호출당 5초 지연)
// 본사 API Server: RestTemplate을 사용하여 지점으로 API 호출
@Slf4j
@RestController
@RequestMapping("/v1/books")
public class SpringMvcHeadOfficeController {
...
@ResponseStatus(HttpStatus.OK)
@GetMapping("/{book-id}")
public ResponseEntity<Book> getBook(@PathVariable("book-id") long bookId) {
URI getBookUri = UriComponentsBuilder.fromUri(baseUri)
.path("/{book-id}")
.build()
.expand(bookId)
.encode()
.toUri(); // http://localhost:7070/v1/books/{book-id}
ResponseEntity<Book> response = restTemplate.getForEntity(getBookUri, Book.class);
Book book = response.getBody();
return ResponseEntity.ok(book);
}
}
// 지점에서 도서 정보 조회후 리턴, 5초 걸리는 작업
@Slf4j
@RestController
@RequestMapping("/v1/books")
public class SpringMvcBranchOfficeController {
...
@ResponseStatus(HttpStatus.OK)
@GetMapping("/{book-id}")
public ResponseEntity<Book> getBook(@PathVariable("book-id") long bookId) throws InterruptedException {
Thread.sleep(5000);
Book book = bookMap.get(bookId);
return ResponseEntity.ok(book);
}
}
- Spring MVC에서 사용하는 RestTemplate을 사용해서 요청 전송
- 응답으로 전달받은 도서 정보를 ResponseEntity클래스를 사용해서 반환
// 본사 API 서버에 도서 정보를 요청하는 클라이언트 PC 역할
// 편의상 본사 API 서버 애플리케이션이 실행될 때 본사 API를 호출하도록 구현
@Slf4j
@SpringBootApplication
public class SpringMvcHeadOfficeApplication {
...
@Bean
public CommandLineRunner run() {
return (String... args) -> {
log.info("# 요청 시작 시간: {}", LocalTime.now());
for (int i = 1; i <= 5; i++) {
Book book = this.getBook(i);
log.info("{}: book name: {}", LocalTime.now(), book.getName());
}
};
}
private Book getBook(long bookId) {
RestTemplate restTemplate = new RestTemplate();
URI getBooksUri = UriComponentsBuilder.fromUri(baseUri)
.path("/{book-id}")
.build()
.expand(bookId)
.encode()
.toUri(); // http://localhost:8080/v1/books/{book-id}
ResponseEntity<Book> response =
restTemplate.getForEntity(getBooksUri, Book.class);
Book book = response.getBody();
return book;
}
}
Spring Webflux
- Non-Blocking 방식 (스레드가 차단되지 않음)
- 다섯개의 책을 조회하는 API호출 지연시간 : 약 5~5.5초 정도
// 본사 API Server: WebClient 사용하여 지점으로 API 호출
@Slf4j
@RequestMapping("/v1/books")
@RestController
public class SpringReactiveHeadOfficeController {
...
@ResponseStatus(HttpStatus.OK)
@GetMapping("/{book-id}")
public Mono<Book> getBook(@PathVariable("book-id") long bookId) {
URI getBookUri = UriComponentsBuilder.fromUri(baseUri)
.path("/{book-id}")
.build()
.expand(bookId)
.encode()
.toUri(); // http://localhost:5050/v1/books/{book-id}
return WebClient.create()
.get()
.uri(getBookUri)
.retrieve()
.bodyToMono(Book.class);
}
}
- Webclient를 사용해서 요청 전송
- 응답으로 전달받은 도서 정보를 포함한 Mono 타입으로 바꾸는 과정을 거친 후 최종적으로 Mono타입의 객체를 반환
- WebClient가 Non-Blocking I/O 방식으로 리액티브 타입을 전송하고 수신하는 역할을 함
Mono
Mono는 Reactor에서 지원하는 Publisher 타입 중 하나로, 단 하나의 데이터만 emit하는 Publisher 객체
일반적으로 HTTP 요청에 대한 응답으로 JSON형식의 응답을 많이 사용하는데, JSON 형식으로 전달되는 응답 내부에는 여러 가지 결과 값이 포함 될 수 있지만 JSON형식의 응답 자체는 하나의 문자열로 구성된 단 하나의 데이터이기 때문에 Mono를 사용하기 가장 적합함
// 지점에서 도서 정보 조회후 리턴, 5초 걸리는 작업
@Slf4j
@RequestMapping("/v1/books")
@RestController
public class SpringReactiveBranchOfficeController {
...
@ResponseStatus(HttpStatus.OK)
@GetMapping("/{book-id}")
public Mono<Book> getBook(@PathVariable("book-id") long bookId)throws InterruptedException {
Thread.sleep(5000);
Book book = bookMap.get(bookId);
log.info("# book for response: {}, {}", book.getBookId(), book.getName());
return Mono.just(book);
}
}
/**
* 본사 API 서버에 도서 정보를 요청하는 검색용 클라이언트 PC 역할을 한다.
*/
@Slf4j
@SpringBootApplication
public class SpringReactiveHeadOfficeApplication {
...
@Bean
public CommandLineRunner run() {
return (String... args) -> {
log.info("# 요청 시작 시간: {}", LocalTime.now());
for (int i = 1; i <= 5; i++) {
int a = i;
// 전달 받은 도서를 처리.
this.getBook(i).subscribe(book -> {
log.info("{}: book name: {}",LocalTime.now(), book.getName());
});
}
};
}
private Mono<Book> getBook(long bookId) {
URI getBooksUri = UriComponentsBuilder.fromUri(baseUri)
.path("/{book-id}")
.build()
.expand(bookId)
.encode()
.toUri(); // http://localhost:6060/v1/books/{book-id}
return WebClient.create()
.get()
.uri(getBooksUri)
.retrieve()
.bodyToMono(Book.class);
}
}
- Spring MVC, restTemplate에선 for문 안에서 this.getBook(i)만 호출해서 전달받은 응답 데이터를 처리
- Spring Webflux, Webclient에선 subscribe()에서 응답 데이터를 전달 받은 후에 처리함
- 본사 API로부터 전달받은 응답이 Mono타입이고, Mono는 Reactor에서 지원하는 Publisher 타입중 하나이기 때문
- Publisher 인터페이스는 subscribe()를 호출해서 전달받은 데이터를 처리하도록 정의되어 있음
- Mono역시 Publisher이므로 subscribe()를 호출해서 전달받은 데이터 처리
Blocking | Non-Blocking |
스레드의 작업이 종료될 때까지 요청 스레드가 차단됨 | 작업 스레드의 종료 여부와 상관없이 요청 스레드가 차단되지않음 |
스레드가 차단되는 문제를 보완하기 위해 멀티스레딩 기법을 사용할 수 있음 |
적은 수의 스레드만 사용해 스레드 전환 비용이 적으므로, CPU를 효율적으로 사용할 수 있음 |
멀티스레딩 기법 사용 시 컨텍스트 스위칭 전환 비용, 메모리 사용 오버헤드, 스레드 풀의 응답 지연등의 문제가 발생할 수 있음 | CPU를 많이 사용하는 작업의 경우에는 성능에 악영향을 미칠 수 있음 |
사용자 요청 처리에서 응답까지 전 과정이 Non-Blocking이어야 제대로 된 효과를 얻을 수 있음 |
728x90