🏗️ Spring Framework 講座【第34回】障害に強い設計!〜リトライとサーキットブレーカー〜

docs

前回、WebClientを使って外部APIと非同期で連携する方法を学びました。これはアプリケーションの応答性を高めます。

しかし、外部サービスは常に利用可能とは限りません。ネットワークの一時的な瞬断、相手サーバーの過負荷、認証エラーなど、外部連携には常に失敗のリスクが伴います。

今回は、このような外部連携における**耐障害性(フォールトトレランス)**を高め、アプリケーション全体の安定性を維持するための、2つの重要な設計パターン、「リトライ」と「サーキットブレーカー」を学びます。


1. リトライ(Retry):一時的な障害への対応

リトライとは、外部APIへのリクエストが一時的なエラー(例:タイムアウト、503 Service Unavailable)で失敗した場合に、一定時間待機した後、自動でリクエストを再試行するパターンです。

  • 目的: 一時的なネットワークの揺らぎや、相手サーバーの軽微な過負荷による失敗を吸収し、最終的な処理成功率を高めること。
  • 注意: 認証エラーなど、何度やっても成功しない恒久的なエラーに対してリトライをしても、無駄に負荷をかけるだけなので、リトライ対象から除外する必要があります。

Springでのリトライ実装:Spring Retry

Spring Bootでは、Spring Retryというライブラリを使うことで、メソッドにアノテーションを付けるだけでリトライ機能を導入できます。

  1. 依存関係の追加: spring-retry を追加します。
  2. 有効化: @EnableRetry をConfigurationクラスに付与します。

Java

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class ExternalApiService {
    
    // @Retryable: このメソッドが例外をスローした場合に再試行を行う
    @Retryable(
        // 再試行の対象となる例外 (TimeoutExceptionが発生した場合)
        retryFor = {TimeoutException.class}, 
        // 最大3回まで再試行する
        maxAttempts = 3, 
        // 最初の試行から2秒待機し、待機時間を1.5倍ずつ増やす(バックオフ戦略)
        backoff = @Backoff(delay = 2000, multiplier = 1.5) 
    )
    public String callExternalApi(String endpoint) throws TimeoutException {
        System.out.println("外部APIに接続を試みます... 実行スレッド: " + Thread.currentThread().getName());
        // ... WebClientによる外部API呼び出し
        
        // 処理が失敗した場合、TimeoutExceptionをスローする
        if (Math.random() < 0.6) { // 60%の確率で失敗をシミュレーション
            throw new TimeoutException("接続タイムアウトが発生しました");
        }
        
        return "データ取得成功";
    }

    // リトライが全て失敗した場合に実行される代替メソッド(フォールバック)
    @Recover 
    public String recover(TimeoutException e, String endpoint) {
        System.err.println("全ての再試行が失敗しました。フォールバック処理を実行。");
        return "デフォルトデータ(フォールバック)";
    }
}

2. サーキットブレーカー(Circuit Breaker):連続的な障害への対応

サーキットブレーカーは、電気回路のブレーカーと同じ役割を果たします。

  • 目的: 外部サービスが連続的に失敗している状態(完全にダウンしている状態)にあるとき、そのサービスへのリクエストを一時的に遮断(短絡)し、呼び出し側アプリケーションを保護すること。

ダウンしているサービスにリトライを続けても無駄な負荷をかけるだけです。サーキットブレーカーは、これを検知し、短絡期間中はすぐにエラーを返すか、デフォルト値を返すように処理を切り替えます。

2-1. 3つの状態遷移

サーキットブレーカーは、以下の3つの状態を遷移します。

  1. Closed(閉鎖): 通常の状態。リクエストをそのまま外部サービスに流す。失敗率が一定の閾値を超えると Open に移行。
  2. Open(開放/遮断): 外部サービスへのリクエストを遮断し、すぐにエラー(またはフォールバック)を返す。一定時間経過すると Half-Open に移行。
  3. Half-Open(半開): 外部サービスが回復したかを確認するため、限定的にリクエストを流し始める。成功すれば Closed に戻り、失敗すれば再び Open に戻る。

Springでの実装:Spring Cloud Circuit Breaker

Spring Bootでは、Resilience4jなどのライブラリをラップした Spring Cloud Circuit Breaker を使って実装するのが一般的です。

Java

import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;

@Service
@RequiredArgsConstructor
public class ProductService {
    
    private final CircuitBreakerFactory circuitBreakerFactory; // DIで注入

    public Product getProductDetail(Long productId) {
        
        // 1. サーキットブレーカーを生成("productCircuit"という名前で管理)
        return circuitBreakerFactory.create("productCircuit")
            
            // 2. 実行する処理(外部API呼び出し)
            .run(
                () -> callExternalApi(productId), // 成功すればこの結果を返す
                
                // 3. フォールバック処理(サーキットブレーカーが開いている、または外部API呼び出しで例外が発生した場合)
                throwable -> {
                    System.err.println("サーキットブレーカー発動。フォールバック処理を実行: " + throwable.getMessage());
                    // 予備のデータや、キャッシュされたデータを返す
                    return new Product(productId, "予備の商品情報", 0); 
                }
            );
    }
    
    // ... 実際の外部API呼び出しロジック
    private Product callExternalApi(Long productId) { /* ... */ return new Product(productId, "正規の商品情報", 5000); }
}

3. リトライとサーキットブレーカーの使い分け

パターン対応する問題適用タイミング
リトライ一時的なエラー(ネットワーク瞬断、短時間の混雑)処理の最初に、短時間で解決できる見込みがある場合。
サーキットブレーカー連続的なエラー、相手サービスのダウン処理の全体を監視し、負荷増大を防ぎたい場合。

通常、リトライを試行しても失敗する場合に、サーキットブレーカーが開くというように、両者を組み合わせて利用することで、より強固な耐障害性のあるシステムを構築します。

✅ 本日のまとめ

  • 耐障害性を高めるための重要なパターンとして、リトライサーキットブレーカーがある。
  • リトライは、@Retryable アノテーションを使い、一時的なエラー発生時に自動で再試行し、成功率を高める。
  • サーキットブレーカーは、外部サービスが連続的に失敗している際にリクエストを**遮断(Open)**し、呼び出し元への負荷を軽減するとともに、相手サービスを保護する。
  • サーキットブレーカーは通常、ClosedOpenHalf-Open の3つの状態を遷移する。
  • フォールバック処理は、リトライ失敗時やサーキットブレーカー発動時に、予備の結果を返すための代替ロジックである。

🔔 次回予告

外部連携の安定性を確保したところで、次はログ管理という、システムの運用とデバッグに不可欠なテーマに移ります。

次回は、Spring Bootで標準のログ機能である LogbackSLF4J の関係、そして本番運用で重要なログレベルの設定方法について学びます。

次回:【第35回】運用に必須の知識!〜ロギングの基本とLogback/SLF4J〜 にご期待ください!

コメント

タイトルとURLをコピーしました