mcpField
S
spring-boot-resilience4j

spring-boot-resilience4j

github.com/giuseppe-trisciuoglio開発3653.8k 回インストール7 週間前に更新
リポジトリ

spring-boot-resilience4j について

Spring Boot 3.xでResilience4jの耐障害パターンを適用するスキルです。設定、テスト、監視の手順を扱い、サーキットブレーカー、リトライ、レート制限、バルクヘッド、タイムリミッター、フォールバックをカバーします。

主な機能

  1. 01サーキットブレーカーとフォールバック
  2. 02指数バックオフ付きリトライ
  3. 03リクエストのレート制限
  4. 04同時実行の分離と時間制限
  5. 05複数の耐障害パターンの組み合わせ
  6. 06Actuatorによる監視と検証

活用シーン

  1. 01外部サービス呼び出しを障害から保護
  2. 02一時的なネットワークエラーの再試行
  3. 03リクエスト率と同時負荷の制限
  4. 04耐障害状態の検証と指標の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-resilience4j

インストール後、Claude Code で/spring-boot-resilience4jを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
configuration-reference.md
examples.md
testing-patterns.md

詳細

Spring Boot Resilience4j のパターン

概要

Spring Boot 3.x でのフォールトトレランスを実現するため、Resilience4j のパターン(サーキットブレーカー、リトライ、レートリミッター、バルクヘッド、タイムリミッター、フォールバック)を、設定およびテストのワークフローとともに提供します。

使用する場面

  • フォールトトレランスを実装し、障害の連鎖を防ぐ
  • サービス呼び出しにサーキットブレーカー、リトライロジック、レート制限を追加する
  • 指数バックオフで一時的な障害に対処する
  • 過負荷やリソース枯渇からサービスを保護する
  • 複数のパターンを組み合わせて、包括的なレジリエンスを実現する

手順

1. セットアップと依存関係

プロジェクトに Resilience4j の依存関係を追加します。Maven の場合は、pom.xml に追加します。

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version> // Use latest stable version
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Gradle の場合は、build.gradle に追加します。

implementation "io.github.resilience4j:resilience4j-spring-boot3:2.2.0"
implementation "org.springframework.boot:spring-boot-starter-aop"
implementation "org.springframework.boot:spring-boot-starter-actuator"

@EnableAspectJAutoProxy を使って AOP アノテーション処理を有効にします(Spring Boot によって自動設定されます)。

2. サーキットブレーカーのパターン

外部サービスを呼び出すメソッドに @CircuitBreaker アノテーションを適用します。

@Service
public class PaymentService {
    private final RestTemplate restTemplate;

    public PaymentService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
    public PaymentResponse processPayment(PaymentRequest request) {
        return restTemplate.postForObject("http://payment-api/process",
            request, PaymentResponse.class);
    }

    private PaymentResponse paymentFallback(PaymentRequest request, Exception ex) {
        return PaymentResponse.builder()
            .status("PENDING")
            .message("Service temporarily unavailable")
            .build();
    }
}

application.yml で設定します。

resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
    instances:
      paymentService:
        baseConfig: default

サーキットブレーカーの設定オプションについて詳しくは、@references/configuration-reference.md を参照してください。

3. リトライのパターン

一時的な障害からの回復には、@Retry アノテーションを適用します。

@Service
public class ProductService {
    private final RestTemplate restTemplate;

    public ProductService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Retry(name = "productService", fallbackMethod = "getProductFallback")
    public Product getProduct(Long productId) {
        return restTemplate.getForObject(
            "http://product-api/products/" + productId,
            Product.class);
    }

    private Product getProductFallback(Long productId, Exception ex) {
        return Product.builder()
            .id(productId)
            .name("Unavailable")
            .available(false)
            .build();
    }
}

application.yml でリトライを設定します。

resilience4j:
  retry:
    configs:
      default:
        maxAttempts: 3
        waitDuration: 500ms
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2
    instances:
      productService:
        baseConfig: default
        maxAttempts: 5

リトライ時の例外設定については、@references/configuration-reference.md を参照してください。

4. レートリミッターのパターン

リクエストレートを制御するには、@RateLimiter を適用します。

@Service
public class NotificationService {
    private final EmailClient emailClient;

    public NotificationService(EmailClient emailClient) {
        this.emailClient = emailClient;
    }

    @RateLimiter(name = "notificationService",
        fallbackMethod = "rateLimitFallback")
    public void sendEmail(EmailRequest request) {
        emailClient.send(request);
    }

    private void rateLimitFallback(EmailRequest request, Exception ex) {
        throw new RateLimitExceededException(
            "Too many requests. Please try again later.");
    }
}

application.yml で設定します。

resilience4j:
  ratelimiter:
    configs:
      default:
        registerHealthIndicator: true
        limitForPeriod: 10
        limitRefreshPeriod: 1s
        timeoutDuration: 500ms
    instances:
      notificationService:
        baseConfig: default
        limitForPeriod: 5
5. バルクヘッドのパターン

リソースを分離するには、@Bulkhead を適用します。同期メソッドには type = SEMAPHORE を使用します。

@Service
public class ReportService {
    private final ReportGenerator reportGenerator;

    public ReportService(ReportGenerator reportGenerator) {
        this.reportGenerator = reportGenerator;
    }

    @Bulkhead(name = "reportService", type = Bulkhead.Type.SEMAPHORE)
    public Report generateReport(ReportRequest request) {
        return reportGenerator.generate(request);
    }
}

非同期/CompletableFuture メソッドには type = THREADPOOL を使用します。

@Service
public class AnalyticsService {
    @Bulkhead(name = "analyticsService", type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture<AnalyticsResult> runAnalytics(
            AnalyticsRequest request) {
        return CompletableFuture.supplyAsync(() ->
            analyticsEngine.analyze(request));
    }
}

application.yml で設定します。

resilience4j:
  bulkhead:
    configs:
      default:
        maxConcurrentCalls: 10
        maxWaitDuration: 100ms
    instances:
      reportService:
        baseConfig: default
        maxConcurrentCalls: 5

  thread-pool-bulkhead:
    instances:
      analyticsService:
        maxThreadPoolSize: 8
6. タイムリミッターのパターン

タイムアウトの境界を設定するには、非同期メソッドに @TimeLimiter を適用します。

@Service
public class SearchService {
    @TimeLimiter(name = "searchService", fallbackMethod = "searchFallback")
    public CompletableFuture<SearchResults> search(SearchQuery query) {
        return CompletableFuture.supplyAsync(() ->
            searchEngine.executeSearch(query));
    }

    private CompletableFuture<SearchResults> searchFallback(
            SearchQuery query, Exception ex) {
        return CompletableFuture.completedFuture(
            SearchResults.empty("Search timed out"));
    }
}

application.yml で設定します。

resilience4j:
  timelimiter:
    configs:
      default:
        timeoutDuration: 2s
        cancelRunningFuture: true
    instances:
      searchService:
        baseConfig: default
        timeoutDuration: 3s
7. 複数のパターンの組み合わせ

単一のメソッドに複数のパターンを重ねて適用し、包括的なフォールトトレランスを実現します。

@Service
public class OrderService {
    @CircuitBreaker(name = "orderService")
    @Retry(name = "orderService")
    @RateLimiter(name = "orderService")
    @Bulkhead(name = "orderService")
    public Order createOrder(OrderRequest request) {
        return orderClient.createOrder(request);
    }
}

実行順序:Retry → CircuitBreaker → RateLimiter → Bulkhead → Method

一貫性を保つため、すべてのパターンで同じ名前の設定インスタンスを参照してください。

8. 例外処理とモニタリング

@RestControllerAdvice を使ってグローバル例外ハンドラーを作成します。

@RestControllerAdvice
public class ResilienceExceptionHandler {

    @ExceptionHandler(CallNotPermittedException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorResponse handleCircuitOpen(CallNotPermittedException ex) {
        return new ErrorResponse("SERVICE_UNAVAILABLE",
            "Service currently unavailable");
    }

    @ExceptionHandler(RequestNotPermitted.class)
    @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
    public ErrorResponse handleRateLimited(RequestNotPermitted ex) {
        return new ErrorResponse("TOO_MANY_REQUESTS",
            "Rate limit exceeded");
    }

    @ExceptionHandler(BulkheadFullException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ErrorResponse handleBulkheadFull(BulkheadFullException ex) {
        return new ErrorResponse("CAPACITY_EXCEEDED",
            "Service at capacity");
    }
}

application.yml で Actuator エンドポイントを有効にし、レジリエンスパターンをモニタリングします。

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,circuitbreakers,retries,ratelimiters
  endpoint:
    health:
      show-details: always
  health:
    circuitbreakers:
      enabled: true
    ratelimiters:
      enabled: true

モニタリングエンドポイントにアクセスします。

  • GET /actuator/health - レジリエンスパターンを含む全体のヘルス状態
  • GET /actuator/circuitbreakers - サーキットブレーカーの状態
  • GET /actuator/metrics - カスタムレジリエンスメトリクス
テストと検証のワークフロー
  1. サーキットブレーカー:障害を発生させるエンドポイントを呼び出す → GET /actuator/circuitbreakers で OPEN と表示されることを確認する → waitDurationInOpenState の時間だけ待つ → 状態が HALF_OPEN → CLOSED と遷移することを確認する

  2. リトライ:resilience4j.retry.metrics.enabled: true を有効にする → エンドポイントを呼び出す → retry.{instance}.successful-calls-with-retry-attempts メトリクスが増加することを確認する

  3. レートリミッター:limitForPeriod を超えるリクエストを送信する → 429 ステータスを確認する → GET /actuator/ratelimiters で LIMITED と表示されることを確認する

  4. バルクヘッド:maxConcurrentCalls を超える同時リクエストで負荷テストを実施する → 超過したリクエストが BulkheadFullException で即座に失敗することを確認する

  5. タイムリミッター:timeoutDuration を超える非同期の遅延をモックする → タイムアウト後にフォールバックが実行されることを確認する

単体テストおよび結合テストの方法については、@references/testing-patterns.md を参照してください。

ベストプラクティス

  • フォールバック方法を用意する:意味のある応答を返しながら、適切に縮退できるようにします
  • 指数バックオフを使用する:復旧中のサービスに過剰な負荷がかからないようにします(exponentialBackoffMultiplier: 2)
  • 適切なしきい値を設定する:failureRateThreshold は 50~70%にします
  • コンストラクターインジェクションを使用する:Resilience4j の依存関係には、フィールドインジェクションを決して使用しないでください
  • ヘルスインジケーターを有効にする:すべてのパターンで registerHealthIndicator: true を設定します
  • 一時的なエラーに対してのみリトライする:ネットワークタイムアウトや 5xx を対象とし、4xx とビジネス例外は除外します
  • 負荷に応じてバルクヘッドのサイズを決める:想定される同時実行数から、スレッドプールとセマフォのサイズを算出します
  • フォールバックの動作を文書化する:フォールバックロジックを明確で予測可能なものにします

制約と警告

  • フォールバックメソッドは、同じシグネチャを持ち、例外パラメーターを任意で追加できます
  • サーキットブレーカーの状態はインスタンスごとに管理されます。マルチテナントのシナリオでは、適切な Bean スコープを設定してください
  • リトライする操作は冪等である必要があります(複数回実行される可能性があります)
  • 必ず完了しなければならない操作には、サーキットブレーカーを使用しないでください。代わりにタイムアウトを使用します
  • レートリミッターはスレッドをブロックする可能性があるため、適切な待機時間を設定してください
  • POST リクエストなど、冪等でない操作で @Retry を使用する場合は注意してください
  • 同時実行数が多いときにスレッドプールバルクヘッドを使用する場合は、メモリを監視してください

例

変更前 → 変更後:サーキットブレーカー
// BEFORE: No protection
public PaymentResponse processPayment(PaymentRequest request) {
    return restTemplate.postForObject("http://payment-api/process", request, PaymentResponse.class);
}

// AFTER: Circuit breaker with fallback
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
public PaymentResponse processPayment(PaymentRequest request) {
    return restTemplate.postForObject("http://payment-api/process", request, PaymentResponse.class);
}
private PaymentResponse paymentFallback(PaymentRequest request, Exception ex) {
    return PaymentResponse.builder().status("PENDING").message("Service temporarily unavailable").build();
}
変更前 → 変更後:バックオフ付きリトライ
// BEFORE: Single attempt
public Order getOrder(Long orderId) {
    return orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException(orderId));
}

// AFTER: Retry with exponential backoff
@Retry(name = "orderService", maxAttempts = 3, waitDuration = @WaitDuration(500L), fallbackMethod = "getOrderFallback")
public Order getOrder(Long orderId) {
    return orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException(orderId));
}
private Order getOrderFallback(Long orderId, Exception ex) { return Order.cachedOrder(orderId); }
変更前 → 変更後:レート制限
// BEFORE: Unbounded requests
@GetMapping("/api/data") public Data fetchData() { return dataService.process(); }

// AFTER: Rate limited
@RateLimiter(name = "dataService", fallbackMethod = "rateLimitFallback")
@GetMapping("/api/data") public Data fetchData() { return dataService.process(); }
private ResponseEntity<ErrorResponse> rateLimitFallback(Exception ex) {
    return ResponseEntity.status(429).body(new ErrorResponse("TOO_MANY_REQUESTS", "Rate limit exceeded"));
}

関連項目: 設定リファレンス · テストパターン · 例 · Resilience4j ドキュメント · Actuator スキル

コメント