1. はじめに 応答しないサービスに過剰なリクエストが送信されるのを避けるために、現在使用されています。たとえば、何らかの理由でサービスが停止した場合、サーキット ブレーカーはその名のとおり、サーキットを遮断します。つまり、競馬の結果を取得するために 100 万件のリクエストが同時に送信されている場合、これらのリクエストを処理できる別のサービスにリダイレクトする必要があります。 Circuit-breakers この他のサービスは、元のサービスのレプリカにすることも、元のサービスの障害に関する他の操作を実行するためにのみ使用することもできます。最終目標は常に、不要な呼び出しを中断し、フローを別の場所で実行することです。2017 に、 Circuit Breaker デザイン パターンをソフトウェア開発設計の最前線に持ち込みました。これは、彼の出版物 Release It!: Design and Deploy Production-Ready Software (Pragmatic Programmers) 1st Edition で行われました。 2017 Michael Nygard 設計パターンは、実際の電子回路や電気回路からヒントを得ています。ただし、一般的な概念として、サーキットブレーカーのアイデアは実際には に によって発明されました。当時と同じように、溢れる電流を処理する必要があります。非常に簡単に言えば、これがこの場合のソフトウェア アーキテクチャに適用しているものです。主な目標は、システムが十分に回復力があることを確認することです。どの程度の回復力が必要で、どの程度の が必要かは、実際にはこのパターンの実装を促進する責任のあるエンジニアの目次第です。その背後にある考え方は、特定の条件下では、特定のリクエストのフローを、同じ の背後にあるより利用可能な別のフローにシームレスにリダイレクトしたい場合があるというものです。 circuit breaker 1879 Thomas Edison fault-tolerant endpoint から へのリクエストを実行するとします。時々、 B が失敗し、 常に利用可能です。 ランダムに失敗した場合には、サービスを完全に利用できるようにするために にアクセスする必要があります。ただし、 にリクエストを返すには、 再びそれほど失敗しないようにする必要があります。その後、 B にランダムにリクエストし、失敗率が一定のレベルに下がった場合にのみ に完全に戻るようにシステムを設定できます。 A B C B C B B B エラー時だけでなくレイテンシ時にもリクエスト C を実行する必要があるかもしれません。 が非常に遅い場合は、すべてのリクエストを に再送信する必要があるかもしれません。定義された試行回数、リクエストの種類、同時スレッド、およびその他の多くのオプションの後に に到達しようとするなど、他の多くの構成が可能です。これは とも呼ばれ、主に一時的な動きです。 B C C short-circuiting サーキット ブレーカーが実際どのようなものであるかについての知識をさらに深めるには、サーキット ブレークがアプリケーション内のエンティティとして機能することを理解する必要があります。サーキット ブレーカーには、主に 3 つのステータスがあります。クローズ、オープン、ハーフ オープンです。クローズ ステータスは、アプリケーション フローが正常に実行されていることを意味します。すべてのリクエストがサービス B に送信されることがわかっているので、サービス A にリクエストを安全に送信できます。オープン状態は、サービス B へのすべてのリクエストが失敗することを意味します。失敗を表すために定義したルールが発生し、サービス B に到達しなくなりました。この場合、常に例外が返されます。 状態は、サーキット ブレーカーがサービス B でテストを実行し、サービス B が再び動作するかどうかを確認するように指示されている状態です。 half-open 成功したリクエストはすべて通常どおり処理されますが、C へのリクエストは継続されます。設定した検証ルールに従って B が期待どおりに動作した場合、サーキット ブレーカーはクローズ状態に戻り、サービス A はサービス B のみにリクエストを送信し始めます。ほとんどのアプリケーションでは、サーキット ブレーカーはデコレータ デザイン パターンに従います。ただし、手動で実装することもできます。ここでは、サーキット ブレーカーを実装する 3 つのプログラム的な方法と、最後に AOP ベースの実装について説明します。コードは で入手できます。 GitHub 2. 車の点検 この記事の最後のポイントでは、カーレース ゲームを見ていきます。ただし、そこに行く前に、 で実行されるアプリケーションを構築するいくつかの側面について説明したいと思います。 circuit breaker 2.1. Kystrix (パリからベルリンへのkystrix-runnable-app) 、小さな として、 によって発明され作成された素晴らしいライブラリです。このライブラリは、Spring や Spring WebFlux との統合など、多くの可能性を提供します。これを見て、少し試してみると面白いでしょう。 Kystrixs DSL Johan Haleby <dependency> <groupId>se.haleby.kystrix</groupId> <artifactId>kystrix-core</artifactId> </dependency> <dependency> <groupId>se.haleby.kystrix</groupId> <artifactId>kystrix-spring</artifactId> </dependency> 私は例を作成しました。これは のモジュール にあります。まず、コードを見てみましょう。 GitHub from-paris-to-berlin-kystrix-runnable-app @GetMapping("/{id}") private fun getCars(@PathVariable id: Int): Mono<Car> { return if (id == 1) Mono.just(Car("Jaguar")) else { hystrixObservableCommand<Car> { groupKey("Test2") commandKey("Test-Command2") monoCommand { webClient.get().uri("/cars/carros/1").retrieve().bodyToMono<Car>() .delayElement(Duration.ofSeconds(1)) } commandProperties { withRequestLogEnabled(true) withExecutionTimeoutInMilliseconds(5000) withExecutionTimeoutEnabled(true) withFallbackEnabled(true) withCircuitBreakerEnabled(false) withCircuitBreakerForceClosed(true) } fallback { Observable.just(Car("Tank1")) } }.toMono() } } このコードは、例のコマンド 2 を表します。コマンド 1 のコードを確認してください。ここで行われていることは、 を使用して必要なコマンドを定義していることです。ここでは、呼び出す必要のあるメソッドを定義します。 では、 状態をオープンに変更するルールを定義します。呼び出しが正確に 1 秒間続くように、呼び出しを明示的に遅延します。 monoCommand commandProperties circuit-breaker 同時に、 ミリ秒のタイムアウトを定義します。これは、タイムアウトに達することがないことを意味します。この例では、 を使用して呼び出しを行うことができます。これは単なるテストなので、 、 を必要としない Jaguar という車の であると想定します。これは、フォールバック メソッドで定義されている Tank1 を取得することもないことも意味します。まだ気づいていない場合は、フォールバック メソッドをよく見てください。このメソッドは を使用します。WebFlux デザイン パターンに従って実装されていますが、 は厳密には Observable ではありません。 5000 Id Id=1 circuit-breaker Id Observable WebFlux Observable Flux ただし、hystrix は両方をサポートしています。アプリケーションを実行し、ブラウザで を開いて、これを確認してください。Spring Boot の起動の非常に早い段階で呼び出しを開始すると、最終的に Tank1 メッセージが表示される可能性があることを理解することが重要です。これは、このプロセスの実行方法に応じて、起動の遅延が 5 秒を簡単に超える可能性があるためです。2 番目の例では、例を Tank 2 に短絡します。 http://localhost:8080/cars/2 @GetMapping("/timeout/{id}") private fun getCarsTimeout(@PathVariable id: Int): Mono<Car> { return if (id == 1) Mono.just(Car("Jaguar")) else { hystrixObservableCommand<Car> { groupKey("Test3") commandKey("Test-Command3") monoCommand { webClient.get().uri("/cars/carros/1").retrieve().bodyToMono<Car>() .delayElement(Duration.ofSeconds(1)) } commandProperties { withRequestLogEnabled(true) withExecutionIsolationThreadInterruptOnTimeout(true) withExecutionTimeoutInMilliseconds(500) withExecutionTimeoutEnabled(true) withFallbackEnabled(true) withCircuitBreakerEnabled(false) withCircuitBreakerForceClosed(true) } fallback { Observable.just(Car("Tank2")) } }.toMono() } } この例では、 がオープン状態になり、応答として Tank 2 を返します。これは、ここでも 1 秒の遅延が発生しているためですが、回路ブレーク条件は 500 ミリ秒後にトリガーするように指定しています。hystrix の動作を知っていれば、 今後何も変わらないことがわかります。この時点で が提供してくれなかったのは、ゲームを作成するために必要なものをシームレスかつ簡単に提供してくれる方法でした。Kystrix クライアント ベースで動作するようです。つまり、メイン サービスの背後にあるサービスにリクエストする前に、コードを宣言する必要があります。 circuit-breaker kystrix hystrix Kystrix 2.2. レジリエンス4J 、サーキット ブレーカーの非常に完全な実装として多くの人に参照されているようです。私の最初の試みは、サーキット ブレーカーのいくつかの重要な側面を調査することでした。つまり、タイムアウトと成功したリクエストの頻度に基づいて機能するサーキット ブレーカーを見てみたかったのです。Resilience4J では、さまざまな種類の モジュールを構成できます。これらは 、 、 、および の の異なるカテゴリに分かれています。これらはすべて、設計パターンの名前でもあります。CircuitBreaker モジュールは このパターンの完全な実装を提供します。 Resilience4J short-circuiting CircuitBreaker Resilience4J Bulkhead Ratelimiter Retry Timelimiter 6 CircuitBreaker 設定できるパラメータは多数ありますが、基本的に モジュールでは、何を失敗と認識するか、半開状態で許可するリクエストの数、および時間またはカウントで設定できるスライディング ウィンドウを設定でき、閉じた状態で発生するリクエストの数を保持します。これは、エラー頻度を計算するために重要です。基本的に、この モジュールはリクエストのレートに役立つと言えますが、必ずしもそうとは限りません。 CircuitBreaker CircuitBreaker どのように解釈するかによって異なります。単に障害に対処する方法として考える方がよいようです。タイムアウトまたは例外のどちらから発生したかに関係なく、ここで障害が処理され、リクエストをシームレスに別の場所にリダイレクトできます。 モジュールは同時リクエストを処理するように設計されています。レート リミッターではありません。 Bulkhead 代わりに、 設計パターンを実装します。これは、1 つのエンドポイントで過度の処理が行われないようにするために使用されます。この場合、 使用すると、リクエストをすべての利用可能なエンドポイントに分散して処理できます。 という名前は、事故が発生した場合に沈没を回避するために大型船に通常備わっているさまざまな密閉された区画に由来しています。船の場合と同様に、スレッド プールで使用できるスレッドの数とリース時間を定義する必要があります。 Bulkhead Bulkhead Bulkhead モジュールは、リクエストのレートを処理するように設計されています。このモジュールと モジュールの重要な違いは、一定のレートまで許容したいという点です。つまり、そのために障害を起こす必要はありません。設計では、一定の値を超えるレートは許容しないと決めるだけです。さらに、リクエストをリダイレクトするか、リクエストを実行する許可が与えられるまで にしておくことができます。Retry モジュールは、他のモジュールとあまり共通点がないため、おそらく最も理解しやすいでしょう。 RateLimiter Bulkhead Retry 基本的に、定義したしきい値に達するまで、特定のエンドポイントへの再試行回数を明示的に宣言します。Timelimiter モジュールは、両方とも の簡略化と見なすことができます。ただし、 スライディング ウィンドウなどの他のパラメーターに依存せず、組み込みの障害しきい値計算もありません。 CircuitBreaker Timelimiter Timelimiter したがって、特定のサービスを呼び出すときにタイムアウトを処理することにのみ興味があり、他の可能性のある障害を考慮しない場合は、 を使用する方がよいでしょう。 Timelimiter 2.2.1. Kotlin と Spring フレームワークなしの Resilience4J (from-paris-to-berlin-resilience4j-runnable-app) このモジュールでは、 kotlin ライブラリのみを使用することにしました。 resilience4j <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-kotlin</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retry</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-ratelimiter</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-timelimiter</artifactId> </dependency> この実装は GitHub のリポジトリで入手できます。まずは パターンを見てみましょう。 TimeLimiter var timeLimiterConfig: TimeLimiterConfig = TimeLimiterConfig.custom() .timeoutDuration(Duration.ofMillis(100)) .build() var timeLimiter: TimeLimiter = TimeLimiter.of("backendName", timeLimiterConfig) private suspend fun getPublicCar(): Car { return timeLimiter.decorateSuspendFunction { getPrivateCar() }.let { suspendFunction -> try { suspendFunction() } catch (exception: Exception) { Car("Opel Corsa") } } } private suspend fun getPrivateCar(): Car { delay(10000) return Car("Lancya") } この場合、関数 を使用して、 機能で装飾しています。これにより、タイムアウトが発生し、呼び出した関数に時間がかかりすぎると、Lancya ではなく Opel Corsa が表示されます。これを試すには、アプリケーションを実行して を開きます。 decorateSuspendFunction function getPrivateCar TimeLimiter http://localhost:8080/cars/timelimiter/normal/1 実装を調べてみると、 取得できないことがわかります。これは、わざと 待ってから返しているからです。TimeLimiter の はこれよりずっと短いため、これは機能しません。TimeLimiter は理解するのがかなり簡単です。一方、 別の話になります。これがその方法の例です。 Lancya 10s TimeLimiter TimeLimiter CircuitBreaker val circuitBreakerConfig = CircuitBreakerConfig.custom() .failureRateThreshold(20f) .slowCallRateThreshold(50f) .slowCallDurationThreshold(Duration.ofMillis(1000)) .waitDurationInOpenState(Duration.ofMillis(1000)) .maxWaitDurationInHalfOpenState(Duration.ofMillis(1000)) .permittedNumberOfCallsInHalfOpenState(500) .minimumNumberOfCalls(2) .slidingWindowSize(2) .slidingWindowType(COUNT_BASED) .build() val circuitBreaker = CircuitBreakerRegistry.of(circuitBreakerConfig).circuitBreaker("TEST") private suspend fun getPublicCar(id: Long): Car { return circuitBreaker.decorateSuspendFunction { getPrivateCar(id) }.let { suspendFunction -> try { suspendFunction() } catch (exception: Exception) { Car("Opel Corsa") } } } private fun getPrivateCar(id: Long): Car { if (id == 2L) { throw RuntimeException() } return Car("Lancya") } この場合、プロパティの失敗率が 未満になったら、サーキット ブレーカーでサーキットを閉じるように指定しています。低速呼び出しにもしきい値がありますが、この場合は 50% 未満になります。低速呼び出しと見なされるには、1 秒以上続く必要があります。また、半開状態の継続時間は 1 秒にする必要があることも指定しています。これは、実際には、オープン状態、半開状態、またはクローズ状態のいずれかになることを意味します。 20% また、最大 500 件の半開状態のリクエストを許可することも言います。エラー計算では、回路ブレーカーはどのマークでそれを実行するかを知る必要があります。これは、回路をいつ閉じるかを決定するために重要です。minimumNumberOfCalls プロパティを使用して、この計算には 2 件のリクエストが 必要であると言います。半開とは、リクエストが安全な失敗しきい値に達した場合に回路を閉じるように試行し続けることを覚えておいてください。 minimumNumberOfCalls この構成では、エラー頻度を計算し、クローズ状態に戻るかどうかを判断するために、スライディング ウィンドウ内で少なくとも つのリクエストを行う必要があります。これは、構成したすべての変数の正確な読み取りです。一般的に、これは、アプリケーションが代替サービスに対して複数の呼び出しを行う可能性があり、代替サービスが存在する場合、半オープン状態中に 80% の成功率が必要であり、オープン状態のタイムアウトが発生している必要があるため、オープン状態からクローズ状態に簡単に切り替わらないことを意味します。 2 このようなタイムアウトを指定する方法は多数あります。この例では、 は 1 秒としています。つまり、チェックがクローズ状態条件を満たさない場合、またはこのタイムアウトがまだ発生していない場合にのみ、 ステータスをオープンのままにします。この で定義された動作は、特定のダウンタイム、レート、およびリクエストのその他の機能を正確に再現することが不可能なため、追跡および予測が困難な場合がありますが、このエンドポイントに対して複数のリクエストを実行すると、上記の動作が経験と一致することがわかります。 maxDurationInHalfOpenState CircuitBreaker CircuitBreaker それでは、エンドポイント と へのリクエストをいくつか実行してみましょう。 1 で終わるのは車の取得に成功したエンドポイントで、 2 で終わるのは指定された車の取得に失敗したエンドポイントです。コードを見ると、 2 以外の値はすべて 応答として取得することを意味します。 は、すぐにランタイム例外をスローすることを意味し、応答として 取得することになります。 http://localhost:8080/cars/circuit/1 http://localhost:8080/cars/circuit/2 Lancya 2 Opel Corsa エンドポイント にリクエストを送信すると、応答として 返され続けます。システムがダウンし始めた場合、つまりエンドポイント 2 にリクエストを送信した場合、しばらくすると への復帰が一定ではなくなることがわかります。 、オープン状態にあり、これ以上のリクエストは許可されないことを通知します。 1 Lancya Lancya System 2021-10-20 09:56:50.492 ERROR 34064 --- [ctor-http-nio-2] .fcbrrcCarControllerCircuitBreaker : io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'TEST' is OPEN and does not permit further calls リクエストが成功すると、サーキット ブレーカーは半開きの状態になります。つまり、正常化する前に、 に戻るリクエストを数回実行する必要があります。Lancya から に数回切り替えてから、再び に戻ります。この数値は 2 と定義しました。これは、エラー計算の最小値です。1 つの失敗のみを引き起こし、失敗していないエンドポイントを呼び出し続けると、何が起こっているかをより明確に把握できます。 Lancya Opel Corsa Lancya 2021-10-20 11:53:29.058 ERROR 34090 --- [ctor-http-nio-4] .fcbrrcCarControllerCircuitBreaker : java.lang.RuntimeException 2021-10-20 11:53:41.102 ERROR 34090 --- [ctor-http-nio-4] .fcbrrcCarControllerCircuitBreaker : io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'TEST' is OPEN and does not permit further calls このオープン ステータス メッセージは、事実ではありますが、非障害エンドポイントに 2 つのリクエストを送信した後に発生しました。これが、状態が半オープンであると言われる理由です。 2.2.2. Spring Boot と AOP なしの Resilience4J (from-paris-to-berlin-resilience4j-spring-app) 前のセクションでは、Spring テクノロジを使用せずに、非常にプログラム的な方法で実装する方法を見てきました。Spring は使用しましたが、 タイプのサービスを提供するためだけに使用しました。さらに、サービス自体については何も変更していません。次のアプリケーションでは、次のライブラリについて説明します。 WebFlux MVC <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-all</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-reactor</artifactId> </dependency> コードがどのように実行されるかを調べてみると、かなり大きな違いがわかります。 @RestController @RequestMapping("/cars") class CarController( private val carService: CarService, timeLimiterRegistry: TimeLimiterRegistry, circuitBreakerRegistry: CircuitBreakerRegistry, bulkheadRegistry: BulkheadRegistry ) { private var timeLimiter: TimeLimiter = timeLimiterRegistry.timeLimiter(CARS) private var circuitBreaker = circuitBreakerRegistry.circuitBreaker(CARS) private var bulkhead = bulkheadRegistry.bulkhead(CARS) @GetMapping("/{id}") private fun getCars(@PathVariable id: Int): Mono<Car> { return carService.getCar() .transform(TimeLimiterOperator.of(timeLimiter)) .transform(CircuitBreakerOperator.of(circuitBreaker)) .transform(BulkheadOperator.of(bulkhead)) .onErrorResume(TimeoutException::class.java, ::fallback) } @GetMapping("/test/{id}") private fun getCarsTest(@PathVariable id: Int): Mono<Car> { return carService.getCar() .transform(TimeLimiterOperator.of(timeLimiter)) .transform(CircuitBreakerOperator.of(circuitBreaker)) .transform(BulkheadOperator.of(bulkhead)) .onErrorResume(TimeoutException::class.java, ::fallback) } @GetMapping("/carros/{id}") private fun getCarros(@PathVariable id: Long): Mono<Car> { return Mono.just(Car("Laborghini")) } private fun fallback(ex: Throwable): Mono<Car> { return Mono.just(Car("Rolls Royce")) } } この例と次の例では、主にタイムアウト プロパティについて見ていきます。これは入門記事なので、 自体の複雑さはあまり重要ではありません。ここで重要なのは、 によって Spring に提供されるデコレータを使用して、これをいかに簡単に実装できるかということです。まだ 方法ではありますが、 から取得した最初のパブリッシャーを、必要な タイプで簡単に装飾できます。 CircuitBreaker Resilience4J programmatical carService.getCar() short-circuit この例では、 、 、 を登録します。最後に、 TimeoutException が発生したときにトリガーされるフォールバック関数を定義します。まだ確認する必要があるのは、これらすべてがどのように構成されているかです。他の構成可能なモジュールと同様に、Spring で Resilience4J を構成します。 を使用します。 TimeLiter BulkHead CircuitBreaker application.yml resilience4j.circuitbreaker: configs: default: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 5 permittedNumberOfCallsInHalfOpenState: 3 automaticTransitionFromOpenToHalfOpenEnabled: true waitDurationInOpenState: 5s failureRateThreshold: 50 eventConsumerBufferSize: 10 recordExceptions: - org.springframework.web.client.HttpServerErrorException - java.util.concurrent.TimeoutException - java.io.IOException ignoreExceptions: # - io.github.robwin.exception.BusinessException shared: slidingWindowSize: 100 permittedNumberOfCallsInHalfOpenState: 30 waitDurationInOpenState: 1s failureRateThreshold: 50 eventConsumerBufferSize: 10 ignoreExceptions: # - io.github.robwin.exception.BusinessException instances: cars: baseConfig: default roads: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 10 permittedNumberOfCallsInHalfOpenState: 3 waitDurationInOpenState: 5s failureRateThreshold: 50 eventConsumerBufferSize: 10 # recordFailurePredicate: io.github.robwin.exception.RecordFailurePredicate resilience4j.retry: configs: default: maxAttempts: 3 waitDuration: 100 retryExceptions: - org.springframework.web.client.HttpServerErrorException - java.util.concurrent.TimeoutException - java.io.IOException ignoreExceptions: # - io.github.robwin.exception.BusinessException instances: cars: baseConfig: default roads: baseConfig: default resilience4j.bulkhead: configs: default: maxConcurrentCalls: 100 instances: cars: maxConcurrentCalls: 10 roads: maxWaitDuration: 10ms maxConcurrentCalls: 20 resilience4j.thread-pool-bulkhead: configs: default: maxThreadPoolSize: 4 coreThreadPoolSize: 2 queueCapacity: 2 instances: cars: baseConfig: default roads: maxThreadPoolSize: 1 coreThreadPoolSize: 1 queueCapacity: 1 resilience4j.ratelimiter: configs: default: registerHealthIndicator: false limitForPeriod: 10 limitRefreshPeriod: 1s timeoutDuration: 0 eventConsumerBufferSize: 100 instances: cars: baseConfig: default roads: limitForPeriod: 6 limitRefreshPeriod: 500ms timeoutDuration: 3s resilience4j.timelimiter: configs: default: cancelRunningFuture: false timeoutDuration: 2s instances: cars: baseConfig: default roads: baseConfig: default このファイルは、リポジトリから取得したサンプル ファイルで、私の例に合わせて変更されています。前に説明したように、さまざまなタイプの のインスタンスには名前があります。さまざまなレジストリとさまざまなリミッターがある場合、名前は非常に重要です。この例では、前に説明したように、timelimiter に注目します。2 秒に制限されていることがわかります。サービスの実装方法を見ると、タイムアウトを強制していることがわかります。 limiters/short-circuit @Component open class CarService { open fun getCar(): Mono<Car> { return Mono.just(Car("Fiat")).delayElement(Duration.ofSeconds(10)); } } アプリケーションを起動し、ブラウザで にアクセスします。Fiat の代わりに されます。これがタイムアウトを定義した方法です。同様に、 簡単に作成できます。 http://localhost:8080/cars/test/2 Rolls Royce Fiat CircuitBreaker 3. 事例 これまで、 と関連するリミッターを実装する 3 つの基本的な方法を見てきました。さらに、私が作成したアプリケーションを使用して、サーキット ブレーカーを実装する私のお気に入りの方法を見ていきます。このアプリケーションは、パリからベルリンに移動するために四角形をクリックするだけの非常にシンプルなゲームです。このゲームは、実装方法を理解できるように作られています。これをどこに実装するかについてはあまり説明されていません。これは、ノウハウを共有するために私が設計したケースにすぎません。 CircuitBreakers いつ知るかは、後で決めてください。基本的には、いくつかの車を作成し、ベルリンへのルートを確立します。このルートのさまざまな場所で、ランダムに問題が発生する都市に到着します。 によって、移動が許可されるまでにどれくらいの時間を待つ必要があるかが決まります。他の車には問題はありません。正しいルートを選択するだけです。 circuit breakers ある都市で特定の分に特定の問題が発生すると登録されている時刻表を確認できます。分マークは、インデックス位置 0 で有効です。つまり、2 は、時計の 2、12、22、32、42、52 分マーク この問題が発生することを意味します。問題には、 と の 2 種類があります。エラーが発生すると、20 秒の遅延が発生します。 every ERROR TIMEOUT タイムアウトにより 50 秒の遅延が発生します。都市が変わるたびに、全員が 10 秒待つ必要があります。ただし、フォールバック方式でこれを行うと、待つ前に車はすでに次の都市の入り口にいます。この場合、次の都市はランダムに選択されます。 4. 実装 以前、 使用して レジストリを構成する方法を見てきました。これを終えたら、関数を装飾する方法の例をいくつか見てみましょう。 application.yml resilience4j @TimeLimiter(name = CarService.CARS, fallbackMethod = "reportTimeout") @CircuitBreaker(name = CarService.CARS, fallbackMethod = "reportError") @Bulkhead(name = CarService.CARS) open fun moveToCity(id: Long): Mono<RoadRace> { val myCar = roadRace.getMyCar() if (!myCar.isWaiting()) { val destination = myCar.location.forward.find { it.id == id } val blockage = destination?.blockageTimeTable?.find { it.minute.toString().last() == LocalDateTime.now().minute.toString().last() } blockage?.let { roadBlockTime -> when (roadBlockTime.blockageType) { BlockageType.TIMEOUT -> return Mono.just(roadRace).delayElement(Duration.ofSeconds(10)) BlockageType.ERROR -> return Mono.create { it.error(BlockageException()) } BlockageType.UNKNOWN -> return listOf(Mono.create { it.error(BlockageException()) }, Mono.just(roadRace).delayElement(Duration.ofSeconds(10))).random() else -> print("Nothing to do here!") } } destination?.let { myCar.delay(10) myCar.location = it myCar.formerLocations.add(myCar.location) } } return Mono.just(roadRace) } private fun reportError(exception: Exception): Mono<RoadRace> { logger.info("---- **** error reported") roadRace.getMyCar().delay(20L) roadRace.getMyCar().randomFw() roadRace.errorReports.add("Error reported! at ${LocalDateTime.now()}") return Mono.create { it.error(BlockageException()) } } private fun reportTimeout(exception: TimeoutException): Mono<RoadRace> { logger.info("---- **** timeout reported!") roadRace.getMyCar().delay(50L) roadRace.getMyCar().randomFw() roadRace.errorReports.add("Timeout reported! at ${LocalDateTime.now()}") return Mono.just(roadRace) } ご覧のとおり、元のサービス呼び出しはアノテーションを使用して直接装飾されています。これは、パッケージに モジュールが存在する場合にのみ実行できます。 AOP <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 、つまり 、 に基づく別のプログラミング パラダイムです。これは を補完するものと考えられており、まさに多くの注釈が機能する方法です。これにより、正確なカット ポイントで元のメソッドの前後または後に他の関数をトリガーできます。例からわかるように、タイムアウトまたはエラーが生成されています。フォールバック メソッド内でも が生成されます。これは問題ではありません。応答を除いて。 AOP Aspect Oriented Programming OOP OOP BlockageException ただし、アプリケーションは 上で実行されているため、このエラーはアプリケーションでは表示されません。ここまでがゲームです。私は、復元力のあるアプリケーションを実装する際にアノテーションを使用すると、作業がはるかに簡単になることを示すことに重点を置いてこれを実装しました。CircuitBreaker だけでなく、 、 、 、 、 などの他のテクノロジも実装しました。これはすべて、CircuitBreaker がアプリケーションでどのように機能するかを確認するために作成されました。このアプリケーションで遊んでみたい場合は、プロジェクトのルートに移動して、以下を実行してください。 WebSockets, WebSockets Spring WebFlux Docker NGINX typescript make docker-clean-build-start 次に、次のコマンドを実行します。 curl -X POST http://localhost:8080/api/fptb/blockage -H "Content-Type: application/json" --data '{"id":1,"name":"Paris","forward":[{"id":2,"name":"Soissons","forward":[{"id":5,"name":"Aken","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":6,"name":"Heerlen","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":7,"name":"Düren","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":3,"name":"Compiègne","forward":[{"id":5,"name":"Aken","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":6,"name":"Heerlen","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":7,"name":"Düren","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":4,"name":"Reims","forward":[{"id":5,"name":"Aken","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":6,"name":"Heerlen","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":7,"name":"Düren","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]}' このリクエストのペイロードは、モジュール 使用して生成されます。このモジュールを調べると、非常に簡単に理解でき、ゲーム用に独自のマップを生成できることがわかります。最後に、 に移動すると、アプリケーションが実行されます。これで、正しい四角形をクリックしてゲームをプレイできます。ただし、勝ちたい場合は赤い四角形をクリックしないでください。ただし、サーキットブレーカーの動作を確認したい場合は、アプリケーションログを実行してください。 from-paris-to-berlin-city-generator http://localhost:9000 docker logs from_paris_to_berlin_web -f 失敗を引き起こすには、赤い四角を明示的にクリックします。 5. KystrixとResilience4Jの違い アプリケーションが小さく、DSL の使用を極力抑えたい場合に最適です。唯一の欠点は、サーキット ブレーカーの影響を受けるメソッドを簡単に装飾できないことです。Resilience4J サーキット ブレーカーを使用するエンタープライズ作業に最適なオプションのようです。アノテーション ベースのプログラミングを提供し、AOP のすべての利点を活用し、モジュールが分離されています。ある意味では、アプリケーションの重要なポイントに戦略的に使用することもできます。また、アプリケーションの多くの側面をカバーする完全なフレームワークとして使用することもできます。 Kystrix Resilience4J 6. 結論 選択するブランドに関係なく、目標は常に回復力のあるアプリケーションを持つことです。この記事では、サーキット ブレーカーの調査を個人的に体験した例と、非常に高いレベルでの調査結果をいくつか示しました。つまり、この記事はサーキット ブレーカーとは何か、リミッターで何ができるのかを知りたい人向けに書かれています。 などの回復力メカニズムを使用してアプリケーションを改善することを考えた場合、可能性は率直に言って無限です。このパターンでは、利用可能なリソースをより有効に活用するためにアプリケーションを微調整できます。主にクラウドでは、コストと実際に割り当てる必要があるリソースの数を最適化することが依然として非常に重要です。 circuit breakers の構成は、Limiters の場合のように簡単な作業ではありません。パフォーマンスと回復力を最適なレベルにするには、すべての構成の可能性を理解する必要があります。これが、この CircuitBreaker の紹介記事で詳細に説明しなかった理由です。 CircuitBreakers さまざまなタイプのアプリケーションに適用できます。ほとんどのメッセージングおよびストリーミング タイプのアプリケーションでは、これが必要になります。大量のデータを処理し、高い可用性も必要とするアプリケーションでは、何らかの形のサーキット ブレーカーを実装できますし、実装する必要があります。大規模なオンライン リテール ストアでは、毎日大量のデータを処理する必要があり、過去には 広く使用されていました。現在、私たちはこれよりも多くの機能を備えた の方向に進んでいるようです。 Circuit-breakers Hystrix Resilience4J 6. 参考文献 https://youtu.be/kR2sm1zelI4 https://youtu.be/AiP2_icXpAk Detekt で Kotlin コードを改善する Resilience 4J スプリングブートデモ Netflix / ヒストリックス Kystrix – Hystrix 用の Kotlin DSL ヨハンハレビー / kystrix レジリエンス4j / レジリエンス4j CircuitBreaker - resilience4j-circuitbreaker を使い始める ありがとう! この記事を私が作成するのと同じくらい楽しんでいただければ幸いです。以下のリンクのいずれかのソーシャル メディアに、レビュー、コメント、フィードバックなどを残してください。この記事をより良いものにするためにご協力いただけると大変ありがたく思います。このアプリケーションのソース コードはすべて お読みいただきありがとうございました。 GitHub に置きました。