[Spring WebFlux] 3. Non-Blocking, WebClient, Mono, subscribe

2025. 4. 11. 23:48·Spring
728x90

2024.06.10 - [CS/OS] - 동기 VS 비동기, 블로킹 VS 논블로킹

 

동기 VS 비동기, 블로킹 VS 논블로킹

동기(Synchronous) vs 비동기(Asynchronous)Synchronous작업을 동시에 수행하거나, 동시에 끝나거나, 끝나는 동시에 시작함Thread1이 작업을 시작시키고, Task1이 끝날때까지 기다렸다 Task2를 시작작업 요청을

karla.tistory.com

 

 

  • 본사에서 클라이언트로부터 도서 정보 조회 요청을 받으면 지점으로 도서 정보 조회 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이어야
제대로 된 효과를 얻을 수 있음

 

 

더보기
더보기

https://github.com/bjpublic/Spring-Reactive/tree/main/part3

 

Spring-Reactive/part3 at main · bjpublic/Spring-Reactive

Contribute to bjpublic/Spring-Reactive development by creating an account on GitHub.

github.com

 

728x90
'Spring' 카테고리의 다른 글
  • [Spring WebFlux] 2. 리액티브 스트림즈(Reactive Streams)
  • [Spring WebFlux] 1. 리액티브 프로그래밍
  • [Spring Webflux] WebClient (HTTP 클라이언트)
  • Spring Event, 스프링 이벤트, ApplicationEventPublisher
Karla Ko
Karla Ko
𝘾𝙤𝙣𝙩𝙞𝙣𝙪𝙤𝙪𝙨𝙡𝙮 𝙄𝙢𝙥𝙧𝙤𝙫𝙞𝙣𝙜, 𝘾𝙤𝙣𝙨𝙩𝙖𝙣𝙩𝙡𝙮 𝘿𝙚𝙫𝙚𝙡𝙤𝙥𝙞𝙣𝙜 𝙔𝙚𝙨!
    250x250
  • Karla Ko
    karlaLog
    Karla Ko
  • 전체
    오늘
    어제
    • Total (467)
      • Spring (19)
      • JPA (4)
      • Cloud & Architecture (15)
        • Kubernetes (5)
        • Docker (3)
        • MSA (2)
        • GCP (1)
        • AWS (4)
      • Devops (1)
      • Message Queue (4)
        • Kafka (2)
        • RabbitMQ (2)
      • Git (4)
      • DB (4)
      • Java (9)
      • Python (4)
      • CS (11)
        • OS (8)
        • Network (2)
        • Algorithm (1)
      • Coding Test (392)
        • programmers (156)
        • Graph (43)
        • DP (37)
        • Search (31)
        • Tree (13)
        • Data Structure (26)
        • Combination (12)
        • Implement (18)
        • Geedy (23)
        • Sort (7)
        • Math (21)
        • geometry (2)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    최소신장트리
    최대공약수
    조합
    프로그래머스
    자료구조
    트리
    플로이드워셜
    백준
    구현
    덱
    그래프
    구간합
    그리디
    LIS
    다익스트라
    파이썬
    최단거리
    알고리즘
    이분탐색
    Algorithm
    큐
    DP
    BFS
    월간코드챌린지
    정렬
    동적계획법
    재귀
    힙
    DFS
    스택
  • hELLO· Designed By정상우.v4.10.3
Karla Ko
[Spring WebFlux] 3. Non-Blocking, WebClient, Mono, subscribe
상단으로

티스토리툴바