이 가이드는 Spring Cloud Circuit Breaker를 사용하여 잠재적으로 실패하는 메서드 호출에 회로 차단기를 적용하는 과정을 안내합니다.
fallback은 일반적으로 프로그램이 예상치 못한 오류 상황에 대비하여 대체 동작을 수행하는 메커니즘을 말합니다. 주로 네트워크 호출, 데이터베이스 연결, 외부 서비스 호출 등과 같은 상황에서 사용됩니다.
스프링 프레임워크에서는 서킷 브레이커 패턴과 함께 사용되며, 서킷 브레이커가 네트워크 호출 등의 작업을 모니터링하고, 지정된 임계값을 초과할 경우에 서킷을 열어 해당 호출을 차단하는 것과 달리, fallback은 오류 발생 시 대체되는 동작을 제공합니다.
일반적으로 fallback은 기본값을 반환하거나, 예상 가능한 값으로 대체하는 등의 방식으로 구현됩니다. 이는 사용자가 예상치 못한 오류 상황에서도 프로그램이 종료되지 않고 대체 동작을 수행하여 안정성을 유지할 수 있도록 도와줍니다.
메서드 호출이 실패할 때 기능을 단계적으로 저하(gracefully degrade functionality)시키기 위해 회로 차단기 패턴을 사용하는 마이크로서비스 애플리케이션을 구축합니다. 회로 차단기 패턴을 사용하면 관련 서비스에 오류가 발생해도 마이크로서비스가 계속 작동하여 오류가 연속적으로 발생하는 것을 방지하고 오류가 발생한 서비스에 복구할 시간을 제공할 수 있습니다.
Server 프로그램인 bookstore와 client 프로그램인 reading 모듈을 추가합니다. bookstore는 의존성으로 Spring Reactive Web을 추가하고 reading은 의존성으로 SpringReactive Web과 Resillience4J를 추가합니다.
Bookstore 서비스에는 단일 엔드포인트가 있습니다. /recommended
에서 액세스할 수 있으며 (단순화를 위해) 권장 읽기 목록을 String
으로된 Mono
로 반환합니다.
BookstoreApplication.java
의 기본 클래스는 다음과 같습니다.
bookstore/src/main/java/hello/BookstoreApplication.java
package hello;
import reactor.core.publisher.Mono;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
@SpringBootApplication
public class BookstoreApplication {
@RequestMapping(value = "/recommended")
public Mono<String> readingList(){
return Mono.just("Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)");
}
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
}
}
@RestController
주석은 @Controller
처럼 BookstoreApplication
을 컨트롤러 클래스로 표시하고 이 클래스의 @RequestMapping
메서드가 @ResponseBody
로 주석이 달린 것처럼 동작하도록 보장합니다. 즉, 이 클래스의 @RequestMapping
메소드의 반환 값은 원래 유형에서 적절하게 자동으로 변환되어 응답 본문에 직접 기록됩니다.
이 애플리케이션을 클라이언트 서비스 애플리케이션과 함께 로컬로 실행하려면 src/main/resources/application.properties
에서 Bookstore 서비스가 클라이언트와 충돌하지 않도록 server.port
를 설정합니다.
bookstore/src/main/resources/application.properties
server.port=8090
Reading 애플리케이션은 Bookstore 애플리케이션의 프런트 엔드입니다. /to-read
에서 읽기 목록을 볼 수 있으며 해당 읽기 목록은 Bookstore 서비스 애플리케이션에서 검색됩니다. reading/src/main/java/hello/ReadingApplication.java
package hello;
import reactor.core.publisher.Mono;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.reactive.function.client.WebClient;
@RestController
@SpringBootApplication
public class ReadingApplication {
@RequestMapping("/to-read")
public Mono<String> toRead() {
return WebClient.builder().build()
.get().uri("http://localhost:8090/recommended").retrieve()
.bodyToMono(String.class);
}
public static void main(String[] args) {
SpringApplication.run(ReadingApplication.class, args);
}
}
bookstore에서 list을 얻으려면 Spring의 WebClient
클래스를 사용합니다. WebClient
는 우리가 제공하는 Bookstore 서비스의 URL에 대해 HTTP GET 요청을 한 다음 결과를 String
으로 된Mono
로 반환합니다. (Spring을 사용하여 WebClient를 사용하여 RESTful 서비스를 사용하는 방법에 대한 자세한 내용은 반응형 RESTful 웹 서비스 구축 가이드를 참조하세요.)
src/main/resources/application.properties
에 server.port
속성을 추가합니다.
reading/src/main/resources/application.properties
server.port=8080
이제 브라우저에서 읽기 애플리케이션의 /to-read
엔드포인트에 액세스하여 읽기 목록을 볼 수 있습니다. 그러나 우리는 Bookstore 애플리케이션에 의존하기 때문에 문제가 발생하거나 Reading 애플리케이션이 Bookstore에 액세스할 수 없는 경우 목록이 없으며 사용자는 불쾌한 HTTP 500
오류 메시지를 받게 됩니다.
Spring Cloud의 Circuit Breaker 라이브러리는 회로 차단기 패턴의 구현을 제공합니다. 회로 차단기에서 메서드 호출을 래핑하면 Spring Cloud 회로 차단기는 해당 메서드에 대한 호출 실패를 감시하고, 오류가 지정된 임계값까지 누적되면(if failures build up to a specified threshold) Spring Cloud 회로 차단기는 후속 호출이 자동으로 실패하도록 회로를 엽니다. 회로가 열려 있는 동안 Spring Cloud 회로 차단기는 호출을 메서드로 리디렉션하고 지정된 fallback 메서드로 전달됩니다.
Spring Cloud Circuit Breaker는 Resilience4J, Hystrix, Sentinal 및 Spring Retry를 포함한 다양한 회로 차단기 구현을 지원합니다. 이 가이드에서는 Resilience4J 구현을 사용합니다. 이 구현을 사용하려면 애플리케이션의 classpath에 spring-cloud-starter-circuitbreaker-reactor-resilience4j
를 추가해야 합니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'guide'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2023.0.0")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
Spring Cloud Circuit Breaker는 애플리케이션을 위한 새로운 회로 차단기를 생성하는 데 사용할 수 있는 ReactiveCircuitBreakerFactory
라는 인터페이스를 제공합니다. 이 인터페이스의 구현은 애플리케이션의 classpath에 있는 스타터를 기반으로 자동 구성됩니다. 이제 이 인터페이스를 사용하여 Bookstore 애플리케이션에 대한 API 호출을 수행하는 새로운 서비스를 만들 수 있습니다.
reading/src/main/java/hello/BookService.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class BookService {
private static final Logger LOG = LoggerFactory.getLogger(BookService.class);
private final WebClient webClient;
private final ReactiveCircuitBreaker readingListCircuitBreaker;
public BookService(ReactiveCircuitBreakerFactory circuitBreakerFactory) {
this.webClient = WebClient.builder().baseUrl("http://localhost:8090").build();
this.readingListCircuitBreaker = circuitBreakerFactory.create("recommended");
}
public Mono<String> readingList() {
return readingListCircuitBreaker.run(webClient.get().uri("recommended").retrieve().bodyToMono(String.class), throwable -> { LOG.warn("Error making request to book service", throwable);
return Mono.just("Cloud Native Java (O'Reilly)");
});
}
}
WebClient
는 리액티브한 방식으로 HTTP 요청을 만들고 응답을 받는 데 사용되는 클래스입니다. 주요 사용 방법은 다음과 같습니다:
WebClient.builder()
를 사용하여 WebClient.Builder
를 생성합니다.baseUrl()
메서드를 사용하여 기본 URL을 설정합니다.build()
메서드를 호출하여 WebClient
인스턴스를 생성합니다.WebClient
인스턴스를 사용하여 다양한 HTTP 메서드(GET, POST 등) 및 요청 구성을 수행합니다.get()
, uri()
, retrieve()
, bodyToMono()
등의 메서드를 사용하여 요청을 구성하고 응답을 처리합니다.ReactiveCircuitBreakerFactory
는 스프링 클라우드의 서킷 브레이커를 생성하기 위한 팩토리입니다. 이 클래스는 주입된 circuitBreakerFactory
를 사용하여 서킷 브레이커를 생성합니다. 생성자에서 이를 참조할 수 있는 이유는 해당 클래스가 외부에서 주입되는 의존성으로 사용되기 때문입니다. 따라서 외부에서 주입되는 의존성을 통해 이를 참조할 수 있습니다.
WebClient.builder().build()
는 기본적인 WebClient
인스턴스를 생성합니다. 반면에 WebClient.builder().baseUrl().build()
는 기본 URL을 설정한 후에 WebClient
인스턴스를 생성합니다. 즉, 두 번째 방법은 기본 URL을 설정한 상태로 WebClient
인스턴스를 생성합니다.
throwable
은 예외를 나타내는 변수입니다. 스프링에서 run()
메서드의 두 번째 매개변수로 전달된 람다식에서 예외가 발생할 경우 해당 예외를 throwable
변수로 받아 처리할 수 있습니다. 보통은 이를 사용하여 예외가 발생했을 때의 처리 로직을 정의합니다.
readingList()
메서드에서 서킷 브레이커를 실행한 후, run()
메서드의 두 번째 매개변수로 전달된 람다식 안에서 예외가 발생한 경우에 대한 처리를 수행합니다. 이 때, throwable
을 사용하여 예외를 받아와 처리하고, return Mono.just("Cloud Native Java (O'Reilly)")
를 사용하여 기본값을 반환합니다. 이러한 사용 패턴은 스프링 컨텍스트를 새로 만들어내는 것이 아니라, 해당 메서드의 일부로서 예외 처리를 수행하는 것입니다. 스프링은 리액티브한 방식으로 이러한 예외 처리를 허용하며, 이를 통해 장애 처리 및 회복 메커니즘을 쉽게 구현할 수 있습니다.
ReactiveCircuitBreakerFactory
에는 새로운 회로 차단기를 생성하는 데 사용할 수 있는 create
라는 단일 메서드가 있습니다. 회로 차단기가 있으면 run
을 호출하기만 하면 됩니다. run
에는 Mono
또는 Flux
와 선택적 Function
이 사용됩니다. 선택적 Function
매개변수는 문제가 발생할 경우 대체 역할을 합니다. 샘플에서 폴백은 String
Cloud Native Java(O'Reilly)
를 포함하는 Mono
를 반환합니다.
새로운 서비스가 준비되면 ReadingApplication
의 코드를 업데이트하여 이 새로운 서비스를 사용할 수 있습니다.
reading/src/main/java/hello/ReadingApplication.java
mport org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@RestController
@SpringBootApplication
public class ReadingApplication {
@Autowired
private BookService bookService;
@RequestMapping("/to-read")
public Mono<String> toRead() {
return bookService.readingList();
}
public static void main(String[] args) {
SpringApplication.run(ReadingApplication.class, args);
}
}
Bookstore 서비스와 Reading 서비스를 모두 실행한 다음 localhost:8080/to-read
에서 Reading 서비스에 대한 브라우저를 엽니다. 전체 권장 읽기 목록이 표시됩니다.
Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
이제 Bookstore 애플리케이션을 종료합니다. 목록 소스는 사라졌지만 Hystrix와 Spring Cloud Netflix 덕분에 공백을 메울 수 있는 신뢰할 수 있는 축약 목록이 생겼습니다.
Cloud Native Java (O'Reilly)