10.8K Views
May 26, 22
スライド概要
2020年4月 #jsug 勉強会での資料です。Netflix Hystrixの代替となるサーキットブレイカーライブラリResilience4jおよびSpring Bootでの利用方法の解説です。
Java、Spring、IntelliJ IDEA
質問はTwitterで #jsug Introduction to Resilience4j (株)カサレアル 多田真敏 2020年4月8日 (C) CASAREAL, Inc. All rights reserved. JSUG勉強会1
質問はTwitterで #jsug 自己紹介 ▸ 多田真敏(@suke̲masa) ▸ 研修トレーナー@カサレアル ▸ Spring / Java EE / Microservices / Cloud Foundry / Kubernetes ▸ Pivotal認定講師 ▸ 日本Springユーザ会スタッフ (C) CASAREAL, Inc. All rights reserved. 2
質問はTwitterで #jsug Resilience4jドキュメントを翻訳しました! ▸ https://github.com/resilience4j-docs-ja/ resilience4j-docs-ja Issue報告、プルリクなど お待ちしています! (C) CASAREAL, Inc. All rights reserved. 3
質問はTwitterで #jsug このセッションの目的 ▸ Resilience4jドキュメントを読んで困らないよう に、背景や仕組みを理解する! ▸ 今回はSpring Bootでのコード例などは少なめ (ドキュメントに沢山あるので) ▸ サンプルコード ▸ https://github.com/MasatoshiTada/resilience4jspring-boot-sample (C) CASAREAL, Inc. All rights reserved. 4
質問はTwitterで #jsug 目次 ▸ Resilience4j登場の背景 ▸ サーキットブレイカーと状態遷移 ▸ Spring Bootでの利用 ▸ Spring Cloud Circuit Breaker (C) CASAREAL, Inc. All rights reserved. 5
質問はTwitterで #jsug 目次 ▸ Resilience4j登場の背景 ▸ サーキットブレイカーと状態遷移 ▸ Spring Bootでの利用 ▸ Spring Cloud Circuit Breaker (C) CASAREAL, Inc. All rights reserved. 6
質問はTwitterで #jsug マイクロサービスは考えることがいっぱい 😇 Design for Failure API Gateway Observability Circuit Breaker Distributed Tracing Polyglot Persistence Orchestration Service Discovery Containerization (C) CASAREAL, Inc. All rights reserved. 7
質問はTwitterで #jsug Spring Cloudとは ▸ マイクロサービスに必要な機能をまとめた ライブラリ ▸ マイクロサービス開発をある程度楽にしてくれる ▸ Spring Bootベース (C) CASAREAL, Inc. All rights reserved. 8
質問はTwitterで #jsug [豆知識]Spring Cloudのバージョン ▸ 英国ロンドン市内の街の名前が付いている Spring Cloud Angel Brixton Camden Dalston Edgware Finchley Greenwich Hoxton 対応するSpring Bootのバージョン ??? ??? ??? ??? 1.5 2.0 2.1 2.2 ←今ココ (C) CASAREAL, Inc. All rights reserved. 9
質問はTwitterで #jsug Netflix OSS ▸ Netflix社はマイクロサービスに必要なライブラリを、 オリジナルのOSSとして作っている ▸ Hystrix (サーキットブレイカー) ▸ Ribbon (ロードバランシング) ▸ Eureka (サービスディスカバリー) ▸ Zuul (APIゲートウェイ) ▸ ・・・ (C) CASAREAL, Inc. All rights reserved. 10
質問はTwitterで #jsug Spring Cloud Netflix ▸ SpringとNetflix OSSを連携させるライブラリ ▸ spring-cloud-starter-netflix-hystrix ▸ spring-cloud-starter-netflix-ribbon ▸ spring-cloud-starter-netflix-eureka-server ▸ spring-cloud-starter-netflix-zuul ▸ ・・・ ▸ 数年前はSpring Cloudの代名詞的存在だった (C) CASAREAL, Inc. All rights reserved. 11
質問はTwitterで #jsug Netflix OSSの多くは保守モードに ▸ 保守モード=積極的には開発されない ▸ HystrixやRibbonは保守モード ▸ EurekaやZuulの開発は継続しているっぽい https://github.com/Netflix/Hystrix (C) CASAREAL, Inc. All rights reserved. 12
質問はTwitterで #jsug SpringOne 2019 Keynoteより ▸ Netflix社はSpringプロジェクトと より連携していくことを発表 https://www.youtube.com/watch?v=mln3_o6qlBo 1:44辺り (C) CASAREAL, Inc. All rights reserved. 13
質問はTwitterで #jsug Spring Cloud Netflixも大半が保守モードへ ▸ spring-cloud-netflix-hystrix ※eurekaは対象外 ▸ spring-cloud-netflix-hystrix-contract ▸ spring-cloud-netflix-hystrix-dashboard ▸ spring-cloud-netflix-hystrix-stream ▸ spring-cloud-netflix-ribbon ▸ spring-cloud-netflix-zuul ▸ spring-cloud-netflix-turbine ▸ spring-cloud-netflix-turbine-stream ▸ spring-cloud-netflix-archaius https://spring.io/blog/2019/01/23/spring-cloud-greenwich-release-is-now-available より (C) CASAREAL, Inc. All rights reserved. 14
質問はTwitterで #jsug Spring Cloud Netflixの代替(あくまで一例) ▸ Hystrix → Resilience4j ▸ Eureka → Spring Cloud Consul ▸ Zuul → Spring Cloud Gateway ▸ Ribbon → Spring Cloud LoadBalancer ▸ Archius → Spring Cloud Config ▸ Turbine → Micrometer + Prometheus ※繰り返しになりますが、Zuul・Eurekaおよびspring-cloud-netflix-eurekaの開発は継続しています (C) CASAREAL, Inc. All rights reserved. 15
質問はTwitterで #jsug Resilience4jとは ▸ Hystrixに代わるサーキットブレイカーライブラリ ▸ 関数型プログラミングモデルが特徴 ▸ 作者はRobert Winkler氏 (@rbrtwnklr) ▸ SpringやNetflixとは独立したプロジェクトだが ▸ HystrixのREADMEには「代わりにResilience4jをオススメ するよ!」と書いてある ▸ Spring Bootとの連携機能を提供している (C) CASAREAL, Inc. All rights reserved. 16
質問はTwitterで #jsug コアモジュール ▸ CircuitBreaker ← 今日はコレ ▸ Bulkhead ▸ RateLimiter ▸ Retry ▸ Cache ▸ TimeLimiter (C) CASAREAL, Inc. All rights reserved. 17
質問はTwitterで #jsug アドオンモジュール ▸ Spring Boot 2 Starter ▸ Custom RxJava2 operators ▸ Retrofit adapter ▸ Custom Spring Reactor operators ▸ Feign adapter ▸ Micrometer Metrics exporter ▸ Circular Buffer Event consumer ▸ Dropwizard Metrics exporter ▸ Kotlin coroutines support ▸ Camel Circuit Breaker ▸ Ratpack Starter ▸ Spring Cloud Circuit Breaker ▸ Vertx Future decorator ▸ http4k resilience module ▸ Prometheus Metrics exporter (C) CASAREAL, Inc. All rights reserved. 18
質問はTwitterで #jsug ざっくりとしたアーキテクチャ CircuitBreakerConfig 生成 CircuitBreakerRegistry 生成 Circuit Breaker Circuit Breaker (C) CASAREAL, Inc. All rights reserved. Circuit Breaker 19
質問はTwitterで #jsug こんなコード // 設定の作成 CircuitBreakerConfig config = CircuitBreakerConfig.ofDefaults(); // Registryの作成 CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config); // サーキットブレイカーの作成 CircuitBreaker cb = registry.circuitBreaker("backendB"); (C) CASAREAL, Inc. All rights reserved. 20
質問はTwitterで #jsug
こんなコード
// バックエンド呼び出しをデコレート
Supplier<String> ds = cb.decorateSupplier(
() -> backendBService.doSomething());
// 呼び出し実行
Try<String> result = Try.ofSupplier(ds)
// 例外時の代替処理
.recover(throwable -> "Recovery");
// 結果を取得
String resultValue = result.get();
(C) CASAREAL, Inc. All rights reserved.
21
質問はTwitterで #jsug 目次 ▸ Resilience4j登場の背景 ▸ サーキットブレイカーと状態遷移 ▸ Spring Bootでの利用 ▸ Spring Cloud Circuit Breaker (C) CASAREAL, Inc. All rights reserved. 22
質問はTwitterで #jsug マイクロサービスはなぜ難しい? ▸ 障害の発生確率が、モノリスより格段に上がるから! ① 分割する=間にネットワークが入る=障害点が増える ▸ 「分散コンピューティングの落とし穴」で検索 ② 稼働率は指数関数的に低下する ▸ 例)99.9% の 10乗 99.0%、99.9% の 30乗 97.0% ③ 頻繁にデプロイ・スケールする=シャットダウンが頻繁 ▸ タイミング悪く、シャットダウン中にリクエストが来るかも (C) CASAREAL, Inc. All rights reserved. 23
質問はTwitterで #jsug Design for Failure ▸ 相手の障害を常に想定して設計すべし ▸ レスポンスが返ってこなかったらどうしよう? ▸ レスポンスがとても遅かったらどうしよう? ▸ ステータスコードやデータ構造が想定外だったら どうしよう? マイクロ サービス A (C) CASAREAL, Inc. All rights reserved. 🔥 マイクロ サービス B 24
質問はTwitterで #jsug サーキットブレイカーの役割 ▸ 相手の障害を検知したらリクエスト送信を止め、 ▸ 無駄な処理をしないようにする ▸ 相手にこれ以上の負荷をかけないようする ④以降はリクエスト送信せず 代替処理のみ実行 ①リクエスト送信 マイクロ サービス A ③代替処理 を実行 🔥 マイクロ サービス B ②異常なレスポンス等 (C) CASAREAL, Inc. All rights reserved. 25
質問はTwitterで #jsug サーキットブレイカーの状態 状態 リクエスト 送信 CLOSED する OPEN しない (正常) (異常) HALF_OPEN (CLOSEDに戻ろうとする) 代替処理 異常なレスポンス時 のみ実行 常に実行 (相手にリクエスト 送信しない) する 異常なレスポンス時 (指定回数のみ) のみ実行 ※もう2つ特別な状態が存在しますが、今回は説明の範囲外とします (C) CASAREAL, Inc. All rights reserved. 26
質問はTwitterで #jsug サーキットブレイカーの状態遷移 CLOSED 失敗率 閾値 or 呼出遅延率 閾値 OPEN ある時間 が経過 失敗率 < 閾値 and 呼出遅延率 < 閾値 HALF̲OPEN 失敗率 閾値 or 呼出遅延率 閾値 (C) CASAREAL, Inc. All rights reserved. 27
質問はTwitterで #jsug 「失敗」「呼出遅延」とは ▸ 失敗=例外の発生 ▸ 対象の例外は設定可能(デフォルトでは全例外が対象) ▸ 呼出遅延=戻り値が返るまでの時間が閾値を超える ▸ 閾値は設定可能(デフォルトは60秒) 例外=失敗、遅い=呼出遅延 CircuitBreaker cb = ...; Supplier<String> ds = cb.decorateSupplier( () -> backendBService.doSomething()); (C) CASAREAL, Inc. All rights reserved. 28
質問はTwitterで #jsug 呼出結果の保存 ▸ 呼出結果(成功、失敗、遅延など)は スライディングウィンドウに保存される ▸ 回数ベース:N個の呼出結果の循環配列で保持 (デフォルト) ▸ 時間ベース:各1秒間の部分集約をN個の循環配列で保持 (C) CASAREAL, Inc. All rights reserved. 29
質問はTwitterで #jsug 回数ベースのスライディングウィンドウ ▸ N個の呼出結果の循環配列で保持 ▸ N+1個目以降の呼出結果は、古い結果を上書きする public class FixedSizeSlidingWindowMetrics implements Metrics { // 循環配列の長さ private final int windowSize; // 循環配列の全要素を集計した結果を保持 private final TotalAggregation totalAggregation; // 個別の呼出結果を保持する循環配列 private final Measurement[] measurements; // 循環配列のインデックス int headIndex; (C) CASAREAL, Inc. All rights reserved. 30
質問はTwitterで #jsug 時間ベースのスライディングウィンドウ ▸ 各1秒間の部分集約をN個の循環配列で保持 ▸ 1回1回の呼出結果は保持しない ▸ N+1秒目以降の呼出結果は、古い結果を上書きする public class SlidingTimeWindowMetrics implements Metrics { // 1秒毎の呼出結果集計を保持する循環配列 final PartialAggregation[] partialAggregations; // 循環配列の長さ private final int timeWindowSizeInSeconds; // 循環配列の全要素を集計した結果を保持 private final TotalAggregation totalAggregation; // 循環配列のインデックス int headIndex; (C) CASAREAL, Inc. All rights reserved. 31
質問はTwitterで #jsug 失敗率、呼出遅延率の計算方法(CLOSED→OPEN) ▸ 失敗率 = (失敗回数 総呼出回数) ▸ 呼出遅延率 = (遅延回数 100 総呼出回数) 100 ▸ 呼出回数が最小呼出回数以上になったら計算される ▸ 最小呼出回数は設定可能(デフォルトは100回) ▸ 逆に言うと、最小呼出回数未満では、全ての呼び出しが 失敗であってもCLOSEDからOPENに遷移しない (C) CASAREAL, Inc. All rights reserved. 32
質問はTwitterで #jsug 失敗率、呼出遅延率の計算方法(HALF̲OPEN→OPEN) ▸ 最大呼出可能回数の設定が必要 ▸ デフォルトは10回、これを超えて呼び出すと例外 ▸ 必ず回数ベースになる ▸ ウィンドウサイズ=最大呼出可能回数 ▸ 最小呼出回数 = min(最大呼出可能回数, CLOSED時の最小呼出回数) (C) CASAREAL, Inc. All rights reserved. 33
質問はTwitterで #jsug 設定:失敗・呼出遅延 CircuitBreakerConfig config = CircuitBreakerConfig.custom() .minimumNumberOfCalls(5) // 最小呼出回数 .failureRateThreshold(50) // 失敗率の閾値(%) .slowCallDurationThreshold( // 呼出遅延の閾値 Duration.ofMillis(1000)) .recordExceptions( // 失敗と見なす例外のリスト AException.class, BException.class) .ignoreExceptions( // 失敗とも成功ともしない例外のリスト CException.class, DException.class) ... .build(); ※Spring Boot利用時はapplication.propertiesに設定可能なので、上記のコードは必要無し (C) CASAREAL, Inc. All rights reserved. 34
質問はTwitterで #jsug 注意 ▸ 呼出遅延の閾値を設定しても、 タイムアウトされる訳ではない!! ▸ 例)閾値を1秒に設定しても、レスポンスに3秒かかっ たらその処理は3秒かかる(1秒で打ち切られない) ▸ タイムアウトするには、RestTemplateなどに タイムアウト設定が必要 (C) CASAREAL, Inc. All rights reserved. 35
質問はTwitterで #jsug OPEN→HALF̲OPENの待ち時間 ①1秒経過 OPEN ②失敗した場合は OPENに戻る HALF̲OPEN ③次は何秒待つ? (C) CASAREAL, Inc. All rights reserved. 36
質問はTwitterで #jsug OPEN→HALF̲OPENの待ち時間計算方法 ▸ IntervalFunctionインタフェース実装クラスの apply()メソッドで指定する ▸ デフォルトで用意されている実装 ① 一定時間だけ待つ(Constant Backoff、デフォルト) ② ランダムな時間待つ(Randomized Backoff) ③ 待ち時間を指数関数的に増やす(Exponential Backoff) ④ ランダム+指数関数(Exponential Random Backoff) (C) CASAREAL, Inc. All rights reserved. 37
質問はTwitterで #jsug 設定:OPEN→HALF̲OPENの待ち時間 // Constant Backoff CircuitBreakerConfig config = CircuitBreakerConfig.custom() .waitDurationInOpenState(Duration.ofSeconds(3)) ... // Randomized Backoff CircuitBreakerConfig config = CircuitBreakerConfig.custom() .waitIntervalFunctionInOpenState( IntervalFunction.ofRandomized()) ... ※Spring Boot利用時はapplication.propertiesに設定可能なので、上記のコードは必要無し (C) CASAREAL, Inc. All rights reserved. 38
質問はTwitterで #jsug 設定:OPEN→HALF̲OPENの待ち時間 // Exponential Backoff CircuitBreakerConfig config = CircuitBreakerConfig.custom() .waitIntervalFunctionInOpenState( IntervalFunction.ofExponentialBackoff( Duration.ofSeconds(1), 2)) ... // Exponential Random Backoff CircuitBreakerConfig config = CircuitBreakerConfig.custom() .waitIntervalFunctionInOpenState( IntervalFunction.ofExponentialRandomBackoff( Duration.ofSeconds(1), 2)) ... ※Spring Boot利用時はapplication.propertiesに設定可能なので、上記のコードは必要無し (C) CASAREAL, Inc. All rights reserved. 39
質問はTwitterで #jsug 主要なクラスのクラス図 (Powered by PlantUML) ※正確なクラス図の記法になっていないかもしれないので、 雰囲気で読んでいください (C) CASAREAL, Inc. All rights reserved. 40
質問はTwitterで #jsug 目次 ▸ Resilience4j登場の背景 ▸ サーキットブレイカーと状態遷移 ▸ Spring Bootでの利用 ▸ Spring Cloud Circuit Breaker (C) CASAREAL, Inc. All rights reserved. 41
質問はTwitterで #jsug Spring Bootとの連携ライブラリ ▸ resilience4j-spring-boot2 ▸ Spring Boot Starter ▸ 別途spring-boot-starter-actuatorと spring-boot-starter-aopも必要 ▸ resilience4j-spring-cloud2 ▸ resilience4j-spring-boot2に Spring Cloud Configとの連携機能を加えたもの (C) CASAREAL, Inc. All rights reserved. 42
質問はTwitterで #jsug Spring Cloud併用時の注意 ▸ spring-cloud-dependenciesを含めていた場合、 Resilience4jのバージョン上書きが必要 ▸ spring-cloud-circuitbreakerが使っているResilience4jの バージョンが古いため ▸ 上書きが必要なのは下記3点 ▸ resilience4j-circuitbreaker ▸ resilience4j-timelimiter ▸ resilience4j-micrometer (C) CASAREAL, Inc. All rights reserved. 43
質問はTwitterで #jsug
application.yml
resilience4j.circuitbreaker:
名前を揃える
instances:
hello-api:
failure-rate-threshold: 50
slow-call-rate-threshold: 100
slow-call-duration-threshold: 1s
permitted-number-of-calls-in-half-open-state: 3
...
public HelloService(CircuitBreakerRegistry registry) {
this.circuitBreaker =
registry.circuitBreaker("hello-api");
}
(C) CASAREAL, Inc. All rights reserved.
44
質問はTwitterで #jsug Auto Configuration対象範囲 application .properties Bean定義済みなので 作成不要 インプット CircuitBreakerConfig 生成 CircuitBreakerRegistry 生成 Circuit Breaker Circuit Breaker (C) CASAREAL, Inc. All rights reserved. Circuit Breaker 45
質問はTwitterで #jsug
使い方
@Service
public class HelloService {
private final CircuitBreaker circuitBreaker;
// コンストラクタインジェクション
public HelloService(
CircuitBreakerRegistry registry) {
this.circuitBreaker =
registry.circuitBreaker("hello-api");
}
(C) CASAREAL, Inc. All rights reserved.
46
質問はTwitterで #jsug
アノテーションスタイル
@CircuitBreaker(name = "hello-api",
fallbackMethod = "helloFallback")
public String hello() {
return restTemplate.getForObject(
helloApiUrl + "/hello", String.class);
}
// 代替処理
public String helloFallback(Throwable t) {
t.printStackTrace();
return "Recover";
}
※この場合はCircuitBreakerインスタンスを生成する必要なし
(C) CASAREAL, Inc. All rights reserved.
47
質問はTwitterで #jsug Actuatorでの監視 ▸ ActuatorでCircuitBreakerの状態を監視できる ▸ /actuator/metrics/resilience4j.circuitbreaker.XXX ▸ /actuator/health ▸ /actuator/circuitbreakers ▸ /actuator/circuitbreakerevents (C) CASAREAL, Inc. All rights reserved. 48
質問はTwitterで #jsug Prometheus利用時の注意点 ▸ resilience4j-prometheusではなく、 resilience4j-micrometer + micrometer-registry-prometheus を使う https://twitter.com/rbrtwnklr/status/1172792997026697216 (C) CASAREAL, Inc. All rights reserved. 49
質問はTwitterで #jsug 目次 ▸ Resilience4j登場の背景 ▸ サーキットブレイカーと状態遷移 ▸ Spring Bootでの利用 ▸ Spring Cloud Circuit Breaker (C) CASAREAL, Inc. All rights reserved. 50
質問はTwitterで #jsug Spring Cloud Circuit Breaker ▸ Springお得意の抽象化ライブラリ ▸ 下記の対応サーキットブレイカーなら、 同じように扱える ▸ Resilience4j ▸ Netflix Hystrix ▸ Alibaba Sentinel ▸ Spring Retry (C) CASAREAL, Inc. All rights reserved. 51
質問はTwitterで #jsug spring-cloud-starter-circuitbreaker-resilience4j ▸ Spring Cloud Circuit Breakerの Resilience4j実装 ▸ CircuitBreakerとTimeLimiterが含まれている ▸ resilience4j-micrometerを追加すれば、 Actuatorエンドポイントを作ることも可能 (C) CASAREAL, Inc. All rights reserved. 52
質問はTwitterで #jsug
こんなコード
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
@Service
public class HelloService {
private final CircuitBreaker circuitBreaker;
public HelloService(...,
CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreaker = circuitBreakerFactory.create("hello-api");
}
public String hello() {
String result = circuitBreaker.run(
() -> restTemplate.getForObject(helloApiUrl + "/hello", String.class),
throwable -> "Recovery");
return result;
}
※@CircuitBreakerのようなアノテーションは提供されていない
(C) CASAREAL, Inc. All rights reserved.
53
質問はTwitterで #jsug
CircuitBreaker#run()のコード
▸ CircuitBreakerだけでなく、TimeLimiterも
適用される
public class Resilience4JCircuitBreaker implements CircuitBreaker {
@Override
public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
// TimeLimiterを適用
Callable restrictedCall = TimeLimiter.decorateFutureSupplier(
timeLimiter, futureSupplier);
// CircuitBreakerを適用
Callable<T> callable = io.github.resilience4j.circuitbreaker.CircuitBreaker
.decorateCallable(defaultCircuitBreaker, restrictedCall);
// 実行&フォールバック処理
}
}
return Try.of(callable::call).recover(fallback).get();
(C) CASAREAL, Inc. All rights reserved.
54
質問はTwitterで #jsug
CircuitBreaker・TimeLimiterの設定
▸ Customizerを利用する
▸ application.propertiesを使いたい場合、
自分でConfigurationPropertiesなどを作る必要あり
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
// 何かカスタマイズするコードを書く
.build();
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
// 何かカスタマイズするコードを書く
}
.build();
return factory -> factory.configureDefault(
id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(timeLimiterConfig)
.circuitBreakerConfig(circuitBreakerConfig)
.build());
(C) CASAREAL, Inc. All rights reserved.
55
質問はTwitterで #jsug Spring Cloud Circuit Breakerを使うか? ▸ 一般に、マイクロサービス関連ライブラリは寿命が短い → それを懸念するなら使う ▸ 抽象化されているので、Resilience4j特有の処理は 書きづらくなる → それが嫌なら使わない ▸ 呼出にTimeLimiterも必ず適用される → それが嫌なら使わない (C) CASAREAL, Inc. All rights reserved. 56
質問はTwitterで #jsug まとめ ▸ Resilience4jは、Netflix Hystrixに代わる 新しいサーキットブレイカーライブラリ ▸ 状態遷移の仕組みを理解することが大切 ▸ Spring Bootとの連携もバッチリ ▸ Spring Cloud Circuit Breakerの利用は お好みで (C) CASAREAL, Inc. All rights reserved. 57