Resilience4j 完全指南

輕量級 Java 容錯庫,提供斷路器、重試、限流、艙壁隔離等彈性模式,專為函數式編程設計。


目錄


什麼是 Resilience4j?

Resilience4j 是一個輕量級的容錯庫,專為 Java 8+ 和函數式編程設計,用於保護應用程式免受外部服務故障的影響。

核心特點

  • 🎯 輕量級:無外部依賴,模組化設計可按需引入
  • 函數式設計:基於高階函數,與 Java 8 Lambda 完美整合
  • 🔧 模組化:六大核心模組可獨立或組合使用
  • 📦 Spring Boot 整合:提供 Starter 支援自動配置
  • 🎨 可觀測性:內建 Metrics 支援 Prometheus、Micrometer

為什麼選擇 Resilience4j?

傳統方式的問題

  • Netflix Hystrix 已停止維護
  • 缺乏容錯機制導致級聯故障
  • 單一服務故障拖垮整個系統

使用 Resilience4j 的優勢

  • 積極維護的現代化替代方案
  • 更輕量、更靈活的 API 設計
  • 原生支援響應式編程(RxJava、Reactor)

與 Hystrix 的比較

特性 Resilience4j Hystrix
維護狀態 活躍開發中 已停止維護
設計理念 函數式 命令式
依賴 無外部依賴 依賴 Archaius
執行緒模型 可選隔離 強制執行緒池隔離
配置方式 程式碼/YAML Archaius

核心模組

Resilience4j 提供六個核心模組,各自解決不同的容錯場景:

模組 功能 使用場景
CircuitBreaker 斷路器 防止對故障服務的持續呼叫
Retry 重試 暫時性故障的自動重試
RateLimiter 限流 控制請求速率,保護下游服務
Bulkhead 艙壁隔離 限制並發,防止資源耗盡
TimeLimiter 超時控制 限制執行時間,快速失敗
Cache 快取 快取結果,減少重複呼叫

模組執行順序

當組合多個模組時,建議的裝飾順序(由外到內):

Retry → CircuitBreaker → RateLimiter → TimeLimiter → Bulkhead → 實際呼叫
// 組合多個模組
Supplier<String> decoratedSupplier = Decorators.ofSupplier(supplier)
    .withRetry(retry)
    .withCircuitBreaker(circuitBreaker)
    .withRateLimiter(rateLimiter)
    .withTimeLimiter(timeLimiter)
    .withBulkhead(bulkhead)
    .decorate();

快速開始

Maven 依賴

<!-- 核心模組 -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>2.2.0</version>
</dependency>

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-retry</artifactId>
    <version>2.2.0</version>
</dependency>

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-ratelimiter</artifactId>
    <version>2.2.0</version>
</dependency>

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
    <version>2.2.0</version>
</dependency>

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-timelimiter</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- Spring Boot Starter(包含所有模組) -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>

Gradle 依賴

// 核心模組
implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
implementation 'io.github.resilience4j:resilience4j-ratelimiter:2.2.0'
implementation 'io.github.resilience4j:resilience4j-bulkhead:2.2.0'
implementation 'io.github.resilience4j:resilience4j-timelimiter:2.2.0'

// Spring Boot 3 Starter
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'

第一個範例

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;

import java.time.Duration;
import java.util.function.Supplier;

public class QuickStart {
    public static void main(String[] args) {
        // 1. 建立 CircuitBreaker 配置
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)                      // 失敗率閾值 50%
            .waitDurationInOpenState(Duration.ofMillis(1000)) // 開啟狀態等待 1 秒
            .slidingWindowSize(10)                         // 滑動窗口大小
            .build();

        // 2. 建立 CircuitBreaker 實例
        CircuitBreaker circuitBreaker = CircuitBreaker.of("myService", config);

        // 3. 裝飾函數
        Supplier<String> decoratedSupplier = CircuitBreaker
            .decorateSupplier(circuitBreaker, () -> callExternalService());

        // 4. 執行
        try {
            String result = decoratedSupplier.get();
            System.out.println("Result: " + result);
        } catch (Exception e) {
            System.out.println("Call failed: " + e.getMessage());
        }
    }

    private static String callExternalService() {
        // 模擬外部服務呼叫
        return "Success";
    }
}

CircuitBreaker 斷路器

斷路器是 Resilience4j 最核心的模組,用於防止對故障服務的持續呼叫。

狀態機

     ┌────────────────────────────────────────┐
     │                                        │
     ▼                                        │
  CLOSED ──────► OPEN ──────► HALF_OPEN ──────┘
     │             │              │
     │ 失敗率       │ 等待時間      │ 測試呼叫
     │ 超過閾值     │ 結束後        │ 成功/失敗
     │             │              │
     └─────────────┴──────────────┘
狀態 說明
CLOSED 正常狀態,請求正常通過
OPEN 斷路狀態,快速失敗,不執行實際呼叫
HALF_OPEN 半開狀態,允許部分請求測試服務是否恢復

配置參數

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    // 失敗率相關
    .failureRateThreshold(50)              // 失敗率閾值(百分比)
    .slowCallRateThreshold(100)            // 慢呼叫率閾值(百分比)
    .slowCallDurationThreshold(Duration.ofSeconds(2)) // 慢呼叫定義

    // 滑動窗口
    .slidingWindowType(SlidingWindowType.COUNT_BASED) // COUNT_BASED 或 TIME_BASED
    .slidingWindowSize(10)                 // 窗口大小
    .minimumNumberOfCalls(5)               // 最小呼叫次數

    // 狀態轉換
    .waitDurationInOpenState(Duration.ofSeconds(10))  // OPEN 狀態等待時間
    .permittedNumberOfCallsInHalfOpenState(3)         // HALF_OPEN 允許的測試呼叫數
    .automaticTransitionFromOpenToHalfOpenEnabled(true) // 自動轉換

    // 例外處理
    .recordExceptions(IOException.class, TimeoutException.class) // 記錄為失敗
    .ignoreExceptions(BusinessException.class)       // 忽略的例外

    .build();

配置參數說明

參數 預設值 說明
failureRateThreshold 50 失敗率閾值,超過則開啟斷路器
slowCallRateThreshold 100 慢呼叫率閾值
slowCallDurationThreshold 60s 定義慢呼叫的時間閾值
slidingWindowType COUNT_BASED 滑動窗口類型
slidingWindowSize 100 滑動窗口大小
minimumNumberOfCalls 100 計算失敗率前的最小呼叫數
waitDurationInOpenState 60s OPEN 狀態的等待時間
permittedNumberOfCallsInHalfOpenState 10 HALF_OPEN 狀態允許的呼叫數

使用範例

// 建立 CircuitBreaker
CircuitBreaker circuitBreaker = CircuitBreaker.of("paymentService", config);

// 方式 1:裝飾 Supplier
Supplier<Payment> supplier = CircuitBreaker
    .decorateSupplier(circuitBreaker, () -> paymentService.process(order));

// 方式 2:裝飾 Function
Function<Order, Payment> function = CircuitBreaker
    .decorateFunction(circuitBreaker, order -> paymentService.process(order));

// 方式 3:裝飾 Runnable
Runnable runnable = CircuitBreaker
    .decorateRunnable(circuitBreaker, () -> notificationService.send(message));

// 方式 4:使用 Try(Vavr 函式庫)
Try<String> result = Try.ofSupplier(
    CircuitBreaker.decorateSupplier(circuitBreaker, () -> callService())
);

// 方式 5:提供 Fallback
String result = circuitBreaker.executeSupplier(() -> callService());
// 或使用 Try 的 recover
Try.ofSupplier(decoratedSupplier)
    .recover(throwable -> "Fallback result")
    .get();

事件監聽

circuitBreaker.getEventPublisher()
    .onSuccess(event -> log.info("Success: {}", event))
    .onError(event -> log.error("Error: {}", event))
    .onStateTransition(event ->
        log.info("State changed from {} to {}",
            event.getStateTransition().getFromState(),
            event.getStateTransition().getToState()))
    .onSlowCallRateExceeded(event -> log.warn("Slow call rate exceeded"))
    .onFailureRateExceeded(event -> log.warn("Failure rate exceeded"));

Retry 重試機制

用於處理暫時性故障,自動重試失敗的操作。

配置參數

RetryConfig config = RetryConfig.custom()
    .maxAttempts(3)                                    // 最大嘗試次數
    .waitDuration(Duration.ofMillis(500))              // 重試間隔
    .intervalFunction(IntervalFunction.ofExponentialBackoff()) // 指數退避
    .retryOnResult(response -> response.getStatus() == 500)    // 根據結果重試
    .retryExceptions(IOException.class, TimeoutException.class) // 重試的例外
    .ignoreExceptions(BusinessException.class)         // 不重試的例外
    .failAfterMaxAttempts(true)                        // 達到最大次數後拋出例外
    .build();

Retry retry = Retry.of("myRetry", config);

退避策略

// 固定間隔
RetryConfig.custom()
    .waitDuration(Duration.ofMillis(500))
    .build();

// 指數退避:500ms, 1000ms, 2000ms, ...
RetryConfig.custom()
    .intervalFunction(IntervalFunction.ofExponentialBackoff(
        Duration.ofMillis(500),  // 初始間隔
        2                        // 乘數
    ))
    .build();

// 隨機化指數退避(防止驚群效應)
RetryConfig.custom()
    .intervalFunction(IntervalFunction.ofExponentialRandomBackoff(
        Duration.ofMillis(500),  // 初始間隔
        2,                       // 乘數
        Duration.ofSeconds(10)   // 最大間隔
    ))
    .build();

// 自定義間隔函數
RetryConfig.custom()
    .intervalFunction(attempt -> Duration.ofMillis(attempt * 100))
    .build();

使用範例

Retry retry = Retry.of("paymentRetry", config);

// 裝飾並執行
Supplier<Payment> retryingSupplier = Retry
    .decorateSupplier(retry, () -> paymentService.process(order));

Try<Payment> result = Try.ofSupplier(retryingSupplier)
    .recover(throwable -> Payment.failed());

// 事件監聽
retry.getEventPublisher()
    .onRetry(event -> log.info("Retry attempt #{}", event.getNumberOfRetryAttempts()))
    .onSuccess(event -> log.info("Success after {} attempts", event.getNumberOfRetryAttempts()))
    .onError(event -> log.error("Failed after {} attempts", event.getNumberOfRetryAttempts()));

RateLimiter 限流器

控制對服務的請求速率,保護系統不被過多請求壓垮。

配置參數

RateLimiterConfig config = RateLimiterConfig.custom()
    .limitRefreshPeriod(Duration.ofSeconds(1))   // 刷新週期
    .limitForPeriod(10)                          // 每週期允許的請求數
    .timeoutDuration(Duration.ofMillis(500))     // 等待許可的超時時間
    .build();

RateLimiter rateLimiter = RateLimiter.of("myRateLimiter", config);

配置參數說明

參數 預設值 說明
limitRefreshPeriod 500ns 許可刷新週期
limitForPeriod 50 每週期的許可數量
timeoutDuration 5s 等待許可的超時時間

使用範例

RateLimiter rateLimiter = RateLimiter.of("apiRateLimiter", config);

// 裝飾函數
Supplier<Response> supplier = RateLimiter
    .decorateSupplier(rateLimiter, () -> apiClient.call());

// 執行(會自動等待許可)
try {
    Response response = supplier.get();
} catch (RequestNotPermitted e) {
    // 超時未獲得許可
    log.warn("Rate limit exceeded");
}

// 動態修改限流配置
rateLimiter.changeLimitForPeriod(20);
rateLimiter.changeTimeoutDuration(Duration.ofSeconds(1));

Bulkhead 艙壁隔離

限制並發執行的數量,防止某個服務耗盡系統資源。

兩種實現方式

類型 說明 適用場景
SemaphoreBulkhead 信號量隔離 同步呼叫、低開銷
ThreadPoolBulkhead 執行緒池隔離 需要完全隔離、非同步執行

SemaphoreBulkhead(信號量)

BulkheadConfig config = BulkheadConfig.custom()
    .maxConcurrentCalls(10)                      // 最大並發數
    .maxWaitDuration(Duration.ofMillis(500))     // 等待進入的超時時間
    .build();

Bulkhead bulkhead = Bulkhead.of("myBulkhead", config);

// 使用
Supplier<String> supplier = Bulkhead
    .decorateSupplier(bulkhead, () -> service.call());

ThreadPoolBulkhead(執行緒池)

ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
    .maxThreadPoolSize(10)                       // 最大執行緒數
    .coreThreadPoolSize(5)                       // 核心執行緒數
    .queueCapacity(20)                           // 佇列容量
    .keepAliveDuration(Duration.ofMillis(100))   // 執行緒存活時間
    .build();

ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("myBulkhead", config);

// 使用(返回 CompletionStage)
CompletionStage<String> stage = bulkhead.executeSupplier(() -> service.call());

TimeLimiter 超時控制

限制非同步操作的執行時間。

配置與使用

TimeLimiterConfig config = TimeLimiterConfig.custom()
    .timeoutDuration(Duration.ofSeconds(2))      // 超時時間
    .cancelRunningFuture(true)                   // 超時時取消 Future
    .build();

TimeLimiter timeLimiter = TimeLimiter.of("myTimeLimiter", config);

// 裝飾 CompletableFuture Supplier
Supplier<CompletableFuture<String>> futureSupplier =
    () -> CompletableFuture.supplyAsync(() -> slowService.call());

Callable<String> callable = TimeLimiter
    .decorateFutureSupplier(timeLimiter, futureSupplier);

try {
    String result = callable.call();
} catch (TimeoutException e) {
    log.warn("Operation timed out");
}

Spring Boot 整合

依賴配置

<!-- Spring Boot 3 -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- AOP 支援 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- Actuator(監控端點) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml 配置

resilience4j:
  # CircuitBreaker 配置
  circuitbreaker:
    instances:
      paymentService:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 3
        slidingWindowType: COUNT_BASED
        recordExceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException
        ignoreExceptions:
          - com.example.BusinessException

      inventoryService:
        slidingWindowSize: 20
        failureRateThreshold: 60
        waitDurationInOpenState: 30s

  # Retry 配置
  retry:
    instances:
      paymentService:
        maxAttempts: 3
        waitDuration: 500ms
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2
        retryExceptions:
          - java.io.IOException

  # RateLimiter 配置
  ratelimiter:
    instances:
      apiLimiter:
        limitRefreshPeriod: 1s
        limitForPeriod: 100
        timeoutDuration: 500ms

  # Bulkhead 配置
  bulkhead:
    instances:
      paymentService:
        maxConcurrentCalls: 10
        maxWaitDuration: 500ms

  # TimeLimiter 配置
  timelimiter:
    instances:
      paymentService:
        timeoutDuration: 2s
        cancelRunningFuture: true

註解使用

@Service
public class PaymentService {

    // 單獨使用 CircuitBreaker
    @CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
    public Payment processPayment(Order order) {
        return paymentGateway.process(order);
    }

    // 組合多個模組
    @CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
    @Retry(name = "paymentService")
    @RateLimiter(name = "apiLimiter")
    @Bulkhead(name = "paymentService")
    @TimeLimiter(name = "paymentService")
    public CompletableFuture<Payment> processPaymentAsync(Order order) {
        return CompletableFuture.supplyAsync(() -> paymentGateway.process(order));
    }

    // Fallback 方法(參數必須一致,加上 Throwable)
    private Payment paymentFallback(Order order, Throwable t) {
        log.error("Payment failed for order: {}, error: {}", order.getId(), t.getMessage());
        return Payment.pending(order.getId());
    }
}

註解執行順序

Spring AOP 代理的執行順序(由外到內):

Retry → CircuitBreaker → RateLimiter → TimeLimiter → Bulkhead → 方法執行

可透過配置調整順序:

resilience4j:
  circuitbreaker:
    circuitBreakerAspectOrder: 1
  retry:
    retryAspectOrder: 2
  ratelimiter:
    rateLimiterAspectOrder: 3

實戰範例

範例 1:電商支付服務

@Service
@Slf4j
public class PaymentService {

    private final PaymentGateway paymentGateway;
    private final PaymentRepository paymentRepository;

    @CircuitBreaker(name = "paymentGateway", fallbackMethod = "processPaymentFallback")
    @Retry(name = "paymentGateway")
    @TimeLimiter(name = "paymentGateway")
    public CompletableFuture<PaymentResult> processPayment(PaymentRequest request) {
        return CompletableFuture.supplyAsync(() -> {
            log.info("Processing payment for order: {}", request.getOrderId());

            PaymentResponse response = paymentGateway.charge(
                request.getAmount(),
                request.getCardToken()
            );

            Payment payment = Payment.builder()
                .orderId(request.getOrderId())
                .amount(request.getAmount())
                .status(PaymentStatus.COMPLETED)
                .transactionId(response.getTransactionId())
                .build();

            paymentRepository.save(payment);
            return PaymentResult.success(payment);
        });
    }

    private CompletableFuture<PaymentResult> processPaymentFallback(
            PaymentRequest request, Throwable t) {
        log.warn("Payment fallback triggered for order: {}, reason: {}",
            request.getOrderId(), t.getMessage());

        // 建立待處理的支付記錄
        Payment pendingPayment = Payment.builder()
            .orderId(request.getOrderId())
            .amount(request.getAmount())
            .status(PaymentStatus.PENDING)
            .errorMessage(t.getMessage())
            .build();

        paymentRepository.save(pendingPayment);
        return CompletableFuture.completedFuture(PaymentResult.pending(pendingPayment));
    }
}

範例 2:外部 API 整合

@Component
@Slf4j
public class ExternalApiClient {

    private final RestTemplate restTemplate;
    private final CircuitBreaker circuitBreaker;
    private final Retry retry;
    private final RateLimiter rateLimiter;

    public ExternalApiClient(
            RestTemplate restTemplate,
            CircuitBreakerRegistry circuitBreakerRegistry,
            RetryRegistry retryRegistry,
            RateLimiterRegistry rateLimiterRegistry) {
        this.restTemplate = restTemplate;
        this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("externalApi");
        this.retry = retryRegistry.retry("externalApi");
        this.rateLimiter = rateLimiterRegistry.rateLimiter("externalApi");
    }

    public ApiResponse callExternalApi(ApiRequest request) {
        // 組合裝飾器
        Supplier<ApiResponse> decoratedSupplier = Decorators
            .ofSupplier(() -> doCall(request))
            .withRetry(retry)
            .withCircuitBreaker(circuitBreaker)
            .withRateLimiter(rateLimiter)
            .decorate();

        return Try.ofSupplier(decoratedSupplier)
            .recover(CallNotPermittedException.class, e -> {
                log.warn("Circuit breaker is open");
                return ApiResponse.serviceUnavailable();
            })
            .recover(RequestNotPermitted.class, e -> {
                log.warn("Rate limit exceeded");
                return ApiResponse.tooManyRequests();
            })
            .recover(Exception.class, e -> {
                log.error("API call failed", e);
                return ApiResponse.error(e.getMessage());
            })
            .get();
    }

    private ApiResponse doCall(ApiRequest request) {
        ResponseEntity<ApiResponse> response = restTemplate.postForEntity(
            "https://api.external.com/endpoint",
            request,
            ApiResponse.class
        );
        return response.getBody();
    }
}

範例 3:資料庫連接保護

@Repository
public class ResilientUserRepository {

    private final JdbcTemplate jdbcTemplate;
    private final CircuitBreaker circuitBreaker;
    private final Bulkhead bulkhead;

    public ResilientUserRepository(
            JdbcTemplate jdbcTemplate,
            CircuitBreakerRegistry circuitBreakerRegistry,
            BulkheadRegistry bulkheadRegistry) {
        this.jdbcTemplate = jdbcTemplate;
        this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("database");
        this.bulkhead = bulkheadRegistry.bulkhead("database");
    }

    public Optional<User> findById(Long id) {
        Supplier<Optional<User>> supplier = Decorators
            .ofSupplier(() -> doFindById(id))
            .withCircuitBreaker(circuitBreaker)
            .withBulkhead(bulkhead)
            .decorate();

        return Try.ofSupplier(supplier)
            .recover(throwable -> Optional.empty())
            .get();
    }

    private Optional<User> doFindById(Long id) {
        try {
            User user = jdbcTemplate.queryForObject(
                "SELECT * FROM users WHERE id = ?",
                new UserRowMapper(),
                id
            );
            return Optional.ofNullable(user);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }
}

監控與指標

Actuator 端點

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

可用端點

  • /actuator/circuitbreakers - 所有斷路器狀態
  • /actuator/circuitbreakers/{name} - 特定斷路器
  • /actuator/circuitbreakerevents - 斷路器事件
  • /actuator/retries - 重試狀態
  • /actuator/ratelimiters - 限流器狀態
  • /actuator/bulkheads - 艙壁狀態

Prometheus / Micrometer 整合

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-micrometer</artifactId>
    <version>2.2.0</version>
</dependency>
@Configuration
public class Resilience4jMetricsConfig {

    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(
            CircuitBreakerRegistry circuitBreakerRegistry,
            RetryRegistry retryRegistry) {
        return registry -> {
            // 綁定 CircuitBreaker 指標
            TaggedCircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry)
                .bindTo(registry);

            // 綁定 Retry 指標
            TaggedRetryMetrics.ofRetryRegistry(retryRegistry)
                .bindTo(registry);
        };
    }
}

主要指標

# CircuitBreaker 狀態
resilience4j_circuitbreaker_state{name="paymentService"}

# 失敗率
resilience4j_circuitbreaker_failure_rate{name="paymentService"}

# 呼叫次數
resilience4j_circuitbreaker_calls_seconds_count{name="paymentService",kind="successful"}
resilience4j_circuitbreaker_calls_seconds_count{name="paymentService",kind="failed"}

# Retry 嘗試次數
resilience4j_retry_calls_total{name="paymentService",kind="successful_without_retry"}
resilience4j_retry_calls_total{name="paymentService",kind="successful_with_retry"}
resilience4j_retry_calls_total{name="paymentService",kind="failed_with_retry"}

最佳實踐

1. 合理設定閾值

# ✅ 推薦:根據實際情況調整
resilience4j:
  circuitbreaker:
    instances:
      criticalService:
        failureRateThreshold: 30      # 關鍵服務,敏感閾值
        slidingWindowSize: 20
        minimumNumberOfCalls: 10

      nonCriticalService:
        failureRateThreshold: 70      # 非關鍵服務,寬鬆閾值
        slidingWindowSize: 50
        minimumNumberOfCalls: 20

# ❌ 不推薦:所有服務使用相同配置

2. Fallback 策略設計

// ✅ 推薦:提供有意義的降級方案
private Payment paymentFallback(Order order, Throwable t) {
    if (t instanceof CallNotPermittedException) {
        // 斷路器開啟,返回待處理狀態
        return Payment.pending(order.getId(), "Service temporarily unavailable");
    } else if (t instanceof TimeoutException) {
        // 超時,記錄並稍後重試
        return Payment.pendingRetry(order.getId());
    }
    // 其他錯誤
    return Payment.failed(order.getId(), t.getMessage());
}

// ❌ 不推薦:簡單返回 null 或拋出例外
private Payment badFallback(Order order, Throwable t) {
    return null;  // 可能導致 NPE
}

3. 日誌與監控

@Configuration
public class Resilience4jEventConfig {

    @Bean
    public RegistryEventConsumer<CircuitBreaker> circuitBreakerEventConsumer() {
        return new RegistryEventConsumer<>() {
            @Override
            public void onEntryAddedEvent(EntryAddedEvent<CircuitBreaker> event) {
                CircuitBreaker cb = event.getAddedEntry();
                cb.getEventPublisher()
                    .onStateTransition(e ->
                        log.info("CircuitBreaker {} state changed: {} -> {}",
                            cb.getName(),
                            e.getStateTransition().getFromState(),
                            e.getStateTransition().getToState()))
                    .onFailureRateExceeded(e ->
                        log.warn("CircuitBreaker {} failure rate exceeded: {}%",
                            cb.getName(), e.getFailureRate()));
            }
            // ... 其他方法
        };
    }
}

4. 測試策略

@SpringBootTest
class PaymentServiceTest {

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @Test
    void shouldOpenCircuitBreakerAfterFailures() {
        CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("paymentService");

        // 重置狀態
        cb.reset();

        // 模擬多次失敗
        for (int i = 0; i < 10; i++) {
            try {
                paymentService.processPayment(failingRequest());
            } catch (Exception ignored) {}
        }

        // 驗證斷路器已開啟
        assertThat(cb.getState()).isEqualTo(CircuitBreaker.State.OPEN);
    }

    @Test
    void shouldUseFallbackWhenCircuitBreakerIsOpen() {
        CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("paymentService");

        // 手動開啟斷路器
        cb.transitionToOpenState();

        // 執行應該使用 fallback
        PaymentResult result = paymentService.processPayment(validRequest()).join();

        assertThat(result.getStatus()).isEqualTo(PaymentStatus.PENDING);
    }
}

常見問題

問題 1:斷路器不會開啟

症狀:失敗很多次但斷路器一直是 CLOSED 狀態

原因

  • minimumNumberOfCalls 未達到
  • 例外類型不在 recordExceptions

解決方案

resilience4j:
  circuitbreaker:
    instances:
      myService:
        minimumNumberOfCalls: 5       # 降低最小呼叫數
        slidingWindowSize: 10
        recordExceptions:
          - java.lang.Exception       # 記錄所有例外

問題 2:Fallback 方法沒有被呼叫

症狀:發生例外但沒有執行 fallback

原因

  • Fallback 方法簽名不正確
  • 方法不在同一個類別中

解決方案

// ✅ 正確:參數一致 + Throwable
@CircuitBreaker(name = "myService", fallbackMethod = "myFallback")
public String myMethod(String param) { ... }

private String myFallback(String param, Throwable t) { ... }

// ❌ 錯誤:缺少參數
private String myFallback(Throwable t) { ... }

// ❌ 錯誤:參數類型不一致
private String myFallback(Integer param, Throwable t) { ... }

問題 3:註解不生效

症狀:加了註解但沒有容錯效果

原因

  • 缺少 AOP 依賴
  • 方法在同一類別內部呼叫(繞過代理)

解決方案

<!-- 確保有 AOP 依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
// ❌ 內部呼叫不會觸發 AOP
@Service
public class MyService {
    public void methodA() {
        methodB();  // 不會觸發 CircuitBreaker
    }

    @CircuitBreaker(name = "myService")
    public void methodB() { ... }
}

// ✅ 注入自己或拆分到不同類別
@Service
public class MyService {
    @Autowired
    private MyService self;

    public void methodA() {
        self.methodB();  // 會觸發 CircuitBreaker
    }

    @CircuitBreaker(name = "myService")
    public void methodB() { ... }
}

問題 4:如何在非 Spring 環境使用

解決方案

// 手動建立 Registry
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
RetryRegistry retryRegistry = RetryRegistry.ofDefaults();

// 取得實例
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("myService");
Retry retry = retryRegistry.retry("myService");

// 使用 Decorators
Supplier<String> decoratedSupplier = Decorators
    .ofSupplier(() -> callService())
    .withRetry(retry)
    .withCircuitBreaker(circuitBreaker)
    .decorate();

String result = decoratedSupplier.get();

總結

核心要點

Resilience4j = CircuitBreaker + Retry + RateLimiter + Bulkhead + TimeLimiter

關鍵優勢

  • ✅ 輕量級、無外部依賴
  • ✅ 函數式設計、易於組合
  • ✅ Spring Boot 完美整合
  • ✅ 豐富的監控指標

模組選擇指南

場景 推薦模組
外部服務不穩定 CircuitBreaker + Retry
防止 API 被濫用 RateLimiter
資源隔離保護 Bulkhead
防止慢請求阻塞 TimeLimiter
暫時性網路故障 Retry

配置速查

# 最常用的 CircuitBreaker 配置
resilience4j:
  circuitbreaker:
    instances:
      default:
        failureRateThreshold: 50
        slowCallRateThreshold: 80
        slowCallDurationThreshold: 2s
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 3

註解速查

@CircuitBreaker(name = "service", fallbackMethod = "fallback")
@Retry(name = "service", fallbackMethod = "fallback")
@RateLimiter(name = "service", fallbackMethod = "fallback")
@Bulkhead(name = "service", fallbackMethod = "fallback")
@TimeLimiter(name = "service", fallbackMethod = "fallback")

參考資源


建立日期:2025-12-01 最後更新:2025-12-01

🔗相關文章