Blocking I/O
- Spring MVC
- RedisTemplate 을 사용하여 동기적으로 API 응답을 주고 받는 방식
- 요청 받은 작업 스레드가 작업을 완료하기 전까지 요청 스레드는 차단
- 따라서 여러 작업을 차단없이 수행하려면 멀티 스레드를 사용해야 하는데 잦은 컨텍스트 스위칭, 스레드를 위한 메모리 할당 증가 등의 문제 존재
e.g. 클라이언트에서 본사 API 로 특정 책에 대한 정보를 요청할 때 본사 서버에서 다시 지점 서버로 요청하는 과정이 있다면,
ClientApplication
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
@Bean
// Application 시작하자마자 실행되는 메서드
public CommandLineRunner run() {
return (String... args) -> {
for (int i = 1; i <= 5; i++) {
Book book = this.getBook(i);
log.info("{}: book name: {}", LocalTime.now(), book.getName());
}
};
}
// RestTemplate 으로 http://localhost:8080/v1/books/{book-id} API 요청
private Book getBook(long bookId) {
RestTemplate restTemplate = new RestTemplate();
URI getBooksUri = UriComponentsBuilder.fromUri(baseUri)
.path("/{book-id}")
.build()
.expand(bookId)
.encode()
.toUri();
ResponseEntity<Book> response =
restTemplate.getForEntity(getBooksUri, Book.class);
Book book = response.getBody();
// 본점 서버(1차 서버)로 부터 데이터를 가져오기 전까지 요청 스레드 차단
return book;
}
}
본사 서버(1차 Network I/O)
@RestController
@RequestMapping("/v1/books")
public class SpringMvcHeadOfficeController {
private final RestTemplate restTemplate;
URI baseUri = UriComponentsBuilder.newInstance().scheme("http")
.host("localhost")
.port(7070)
.path("/v1/books")
.build()
.encode()
.toUri();
public SpringMvcHeadOfficeController(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@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}
// 2차 API 서버(지점 서버)로 요청 전송
ResponseEntity<Book> response = restTemplate.getForEntity(getBookUri, Book.class);
Book book = response.getBody();
// 지점 서버에서 요청 응답이 오기 전까지 해당 스레드 차단
return ResponseEntity.ok(book);
}
}
지점 서버(2차 Network I/O)
@RestController
@RequestMapping("/v1/books")
public class SpringMvcBranchOfficeController {
private Map<Long, Book> bookMap;
@Autowired
public SpringMvcBranchOfficeController(Map<Long, Book> bookMap) {
this.bookMap = bookMap;
}
// bookMap 에서 특정 book-id 에 대한 Book 객체를 찾아 리턴
@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);
}
}
Non-Blocking I/O
- Spring WebFlux
- WebClient 를 이용하여 비동기적으로 API 응답을 주고받는 방식
- 요청 받은 스레드가 작업을 완료하지 않았더라도 요청 스레드는 차단되지 않기 때문에 실행 시간 단축 가능
- 대량의 트래픽 요청을 받거나, MSA 기반 시스템에서 NW I/O 사이의 지연을 단축시키고자 할 때 유용
ClientApplication
@SpringBootApplication
public class ClientApplication {
private URI baseUri = UriComponentsBuilder.newInstance().scheme("http")
.host("localhost")
.port(6060)
.path("/v1/books")
.build()
.encode()
.toUri();
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
@Bean
public CommandLineRunner run() {
return (String... args) -> {
for (int i = 1; i <= 5; i++) {
this.getBook(i)
.subscribe( // Mono 로 전달받은 Publisher 를 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}
// WebClient로 본사 서버에 데이터 요청, Mono(Publisher) 객체 리턴
return WebClient.create()
.get()
.uri(getBooksUri)
.retrieve()
.bodyToMono(Book.class);
}
}
본사 서버(1차 Network I/O)
@RequestMapping("/v1/books")
@RestController
public class SpringReactiveHeadOfficeController {
URI baseUri = UriComponentsBuilder.newInstance().scheme("http")
.host("localhost")
.port(5050)
.path("/v1/books")
.build()
.encode()
.toUri();
@Autowired
public 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}
// 2차 서버로 요청 전송 + Mono 객체 전달
return WebClient.create()
.get()
.uri(getBookUri)
.retrieve()
.bodyToMono(Book.class);
}
}
지점 서버(2차 Network I/O)
@RequestMapping("/v1/books")
@RestController
public class SpringReactiveBranchOfficeController {
private Map<Long, Book> bookMap;
@Autowired
public SpringReactiveBranchOfficeController(Map<Long, Book> bookMap) {
this.bookMap = bookMap;
}
@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);
// Mono 객체 만들어 리턴
return Mono.just(book);
}
}
Spring MVC (Blocking I/O)로 5번 API 요청시 1번 응답을 받는데 걸린 시간(5s) * 5번 = 25s,
Spring WebFlux(Non-Blocking I/O)로 5번 API 요청시 요청 스레드가 차단 되지 않아 전체 요청을 수행하는데 5s 소요
'ㄴ Spring Webflux > 스프링으로 시작하는 리액티브 프로그래밍' 카테고리의 다른 글
Ch06 마블 다이어그램 (0) | 2024.09.19 |
---|---|
Ch05 Reactor 개요 (0) | 2024.09.18 |
Ch04 리액티브 프로그래밍을 위한 사전 지식 (0) | 2024.09.17 |
Ch02 리액티브 스트림즈(Reactive Streams) (0) | 2024.08.28 |
Ch01 리액티브 시스템과 리액티브 프로그래밍 (0) | 2024.08.22 |
댓글