前回、WebClientを使って外部APIと非同期で連携する方法を学びました。これはアプリケーションの応答性を高めます。
しかし、外部サービスは常に利用可能とは限りません。ネットワークの一時的な瞬断、相手サーバーの過負荷、認証エラーなど、外部連携には常に失敗のリスクが伴います。
今回は、このような外部連携における**耐障害性(フォールトトレランス)**を高め、アプリケーション全体の安定性を維持するための、2つの重要な設計パターン、「リトライ」と「サーキットブレーカー」を学びます。
1. リトライ(Retry):一時的な障害への対応
リトライとは、外部APIへのリクエストが一時的なエラー(例:タイムアウト、503 Service Unavailable)で失敗した場合に、一定時間待機した後、自動でリクエストを再試行するパターンです。
- 目的: 一時的なネットワークの揺らぎや、相手サーバーの軽微な過負荷による失敗を吸収し、最終的な処理成功率を高めること。
- 注意: 認証エラーなど、何度やっても成功しない恒久的なエラーに対してリトライをしても、無駄に負荷をかけるだけなので、リトライ対象から除外する必要があります。
Springでのリトライ実装:Spring Retry
Spring Bootでは、Spring Retryというライブラリを使うことで、メソッドにアノテーションを付けるだけでリトライ機能を導入できます。
- 依存関係の追加:
spring-retryを追加します。 - 有効化:
@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つの状態を遷移します。
- Closed(閉鎖): 通常の状態。リクエストをそのまま外部サービスに流す。失敗率が一定の閾値を超えると Open に移行。
- Open(開放/遮断): 外部サービスへのリクエストを遮断し、すぐにエラー(またはフォールバック)を返す。一定時間経過すると Half-Open に移行。
- 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)**し、呼び出し元への負荷を軽減するとともに、相手サービスを保護する。
- サーキットブレーカーは通常、Closed → Open → Half-Open の3つの状態を遷移する。
- フォールバック処理は、リトライ失敗時やサーキットブレーカー発動時に、予備の結果を返すための代替ロジックである。
🔔 次回予告
外部連携の安定性を確保したところで、次はログ管理という、システムの運用とデバッグに不可欠なテーマに移ります。
次回は、Spring Bootで標準のログ機能である Logback と SLF4J の関係、そして本番運用で重要なログレベルの設定方法について学びます。
次回:【第35回】運用に必須の知識!〜ロギングの基本とLogback/SLF4J〜 にご期待ください!


コメント