spring-boot-resilience4j について
Spring Boot 3.xでResilience4jの耐障害パターンを適用するスキルです。設定、テスト、監視の手順を扱い、サーキットブレーカー、リトライ、レート制限、バルクヘッド、タイムリミッター、フォールバックをカバーします。
主な機能
- 01サーキットブレーカーとフォールバック
- 02指数バックオフ付きリトライ
- 03リクエストのレート制限
- 04同時実行の分離と時間制限
- 05複数の耐障害パターンの組み合わせ
- 06Actuatorによる監視と検証
活用シーン
- 01外部サービス呼び出しを障害から保護
- 02一時的なネットワークエラーの再試行
- 03リクエスト率と同時負荷の制限
- 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.mdexamples.mdtesting-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- カスタムレジリエンスメトリクス
テストと検証のワークフロー
-
サーキットブレーカー:障害を発生させるエンドポイントを呼び出す →
GET /actuator/circuitbreakersでOPENと表示されることを確認する →waitDurationInOpenStateの時間だけ待つ → 状態がHALF_OPEN→CLOSEDと遷移することを確認する -
リトライ:
resilience4j.retry.metrics.enabled: trueを有効にする → エンドポイントを呼び出す →retry.{instance}.successful-calls-with-retry-attemptsメトリクスが増加することを確認する -
レートリミッター:
limitForPeriodを超えるリクエストを送信する → 429 ステータスを確認する →GET /actuator/ratelimitersでLIMITEDと表示されることを確認する -
バルクヘッド:
maxConcurrentCallsを超える同時リクエストで負荷テストを実施する → 超過したリクエストがBulkheadFullExceptionで即座に失敗することを確認する -
タイムリミッター:
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 スキル