Spring Cloud Circuit Breaker https://spring.io/projects/spring-cloud-circuitbreaker#overview のチュートリアルレベルのことをやる。
Spring Cloud Circuit Breaker自身は抽象APIを提供するだけで、使う際には具体的な実装を選択する、というタイプ。以前のJSUG勉強会でResilience4Jが良いとかなんとか聞いた記憶があるので、今回はこれを使う。
とりあえず使ってみる
plugins {
id 'org.springframework.boot' version '2.2.7.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
sourceCompatibility = '11'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "Hoxton.SR4")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class Application { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); } }
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class DemoController { private RestTemplate rest; private CircuitBreakerFactory cbFactory; public DemoController(RestTemplate rest, CircuitBreakerFactory cbFactory) { this.rest = rest; this.cbFactory = cbFactory; } @GetMapping("/sample") public String sample() { return cbFactory.create("sample").run( () -> rest.getForObject("http://localhost:8080/hoge", String.class), throwable -> "fallback"); } @GetMapping("/hoge") public String slow() { return "hoge"; } }
これでhttp://localhost:8080/sampleにアクセスするとhogeと返ってくる。
で、以下のように意図的に実行時例外を発生させてみると、fallbackと返ってくる。
return cbFactory.create("slow").run( () -> rest.getForObject("invalid-url", String.class), throwable -> "fallback"); //不正なURL文字列
設定変更
デフォルト設定変更
特に何も設定しない状態だと1秒でタイムアウトする。ので、デフォルトのタイムアウト設定を変更してみる。以下はほぼチュートリアルからコピペしてきたもの。5秒タイムアウトにしてある。
@Bean public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() { return factory -> {factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(5)).build()) .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()) .build()); }; }
デモ用に6秒スリープするエンドポイントを用意し、そこへアクセスする。こうすると5秒でタイムアウト、fallbackしてfallbackが返ってくる。
@GetMapping("/sample") public String sample() { return cbFactory.create("sample").run(() -> rest.getForObject("http://localhost:8080/slow", String.class), throwable -> "fallback"); } @GetMapping("/slow") public String slow() throws InterruptedException { TimeUnit.SECONDS.sleep(6L); return "slow"; }
指定
次に、それぞれのCircuitBreakerごとに異なる設定をしてみる。
以下のように、デフォルトは10秒、idがtimeout-3secは3秒でタイムアウトに設定する。
@Bean public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() { return factory -> { factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build()) .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()).build()); factory.configure( c -> c.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build()).build(), "timeout-3sec"); }; }
動作確認用のcontrollerをつくる。/sample2のところはidをtimeout-3secにするのがポイント。
@GetMapping("/sample") public String sample() { return cbFactory.create("sample").run(() -> rest.getForObject("http://localhost:8080/slow", String.class), throwable -> "fallback"); } @GetMapping("/sample2") public String sample2() { return cbFactory.create("timeout-3sec").run(() -> rest.getForObject("http://localhost:8080/slow", String.class), throwable -> "fallback"); } @GetMapping("/slow") public String slow() throws InterruptedException { TimeUnit.SECONDS.sleep(6L); return "slow"; }
この状態で、