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"; }
この状態で、