知識庫 / Networking RSS 訂閱

Resilience4j 事件端點

Networking,Spring Boot
HongKong
5
11:41 AM · Dec 06 ,2025

1. 概述

本文將探討 Resilience4j 內部使用的事件機制,以及在 SpringBoot 應用中列出這些事件的端點。

我們將重用我們在《Resilience4j 與 Spring Boot 指南》文章中所使用的項目,以展示 Resilience4j 如何通過 actuator 端點列出不同的事件模式。

2. 模式事件

該庫內部使用事件來驅動彈性模式的行為(允許或拒絕調用),作為一種通信機制。此外,事件為監控和可觀測性提供有價值的信息,也有助於故障排除。

此外,斷路器、重試、速率限制器、隔離器和時間限制器實例發出的事件,分別存儲在循環事件消費者緩衝區中。緩衝區的大小可以通過 <em >eventConsumerBufferSize</em> 屬性進行配置,默認值為 100 個事件。

我們將詳細瞭解每個模式下發出的特定事件列表,在操作器端點中進行查看。

3. 熔斷器

熔斷器是一種保護電路的設備,當電路中的電流超過其額定值時,會自動切斷電路,從而防止設備損壞或火災。 熔斷器通常用於保護電路中的負載,例如電機、燈具和電子設備。

熔斷器的工作原理是基於電流的瞬時變化。當電流突然增加時,熔斷器會迅速響應,並切斷電路。 這種快速響應可以防止電流過載,從而保護設備和電路。

熔斷器有多種類型,包括:

  • 時間動作型熔斷器: 這些熔斷器根據電流的持續時間來工作。
  • 快速動作型熔斷器: 這些熔斷器對電流的瞬時變化更敏感,可以更快地切斷電路。
  • 熱敏電阻熔斷器: 這些熔斷器使用熱敏電阻來檢測電流,並根據電流的大小來控制熔斷器的動作。

選擇合適的熔斷器取決於具體的應用場景和電路要求。

3.1. 配置

我們將提供針對我們定義的 /api/circuit-breaker 端點的 Circuit Breaker 實例的默認配置:

resilience4j.circuitbreaker:
  configs:
    default:
      registerHealthIndicator: true
      slidingWindowSize: 10
      minimumNumberOfCalls: 5
      permittedNumberOfCallsInHalfOpenState: 3
      automaticTransitionFromOpenToHalfOpenEnabled: true
      waitDurationInOpenState: 5s
      failureRateThreshold: 50
      eventConsumerBufferSize: 50
  instances:
    externalService:
      baseConfig: default

3.2. 事件

Resilience4j 在 actuator 端點下暴露了 Circuit Breaker 相關的事件:

http://localhost:8080/actuator/circuitbreakers

斷路器是最高級的彈性恢復機制,並且定義了最多樣的事件類型。它的實現依賴於狀態機概念,利用事件來指示狀態轉換。因此,讓我們來看看在從初始 CLOSED 狀態過渡到 OPEN 狀態以及返回到 CLOSED 狀態時,在活動事件端點定義的事件。

對於成功的調用,我們可以看到 CircuitBreakerOnSuccess 事件:

{
    "circuitBreakerName": "externalService",
    "type": "SUCCESS",
    "creationTime": "2023-03-22T16:45:26.349252+02:00",
    "errorMessage": null,
    "durationInMs": 526,
    "stateTransition": null
}

讓我們看看當電路斷路器實例處理失敗請求時會發生什麼:

@Test
void testCircuitBreakerEvents() throws Exception {
    EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
      .willReturn(serverError()));

    IntStream.rangeClosed(1, 5)
      .forEach(i -> {
        ResponseEntity<String> response = restTemplate.getForEntity("/api/circuit-breaker", String.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
      });
    ...
}

我們可以觀察到,當出現故障請求時,CircuitBreakerOnErrorEvent 會被觸發:

{
"circuitBreakerName": "externalService",
"type": "ERROR",
"creationTime": "2023-03-19T20:13:05.069002+02:00",
"errorMessage": "org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 Server Error: \"{\"error\": \"Internal Server Error\"}\"",
"durationInMs": 519,
"stateTransition": null
}

此外,這些成功/錯誤事件包含 durationInMs 屬性,這是一個有用的性能指標。

當故障率超過配置閾值時,實例會觸發 CircuitBreakerOnFailureRateExceededEvent 事件,從而確定狀態過渡到 OPEN 狀態並觸發 CircuitBreakerOnStateTransitionEvent  事件。

{
"circuitBreakerName": "externalService",
"type": "FAILURE_RATE_EXCEEDED",
"creationTime": "2023-03-19T20:13:07.554813+02:00",
"errorMessage": null,
"durationInMs": null,
"stateTransition": null
},
{
"circuitBreakerName": "externalService",
"type": "STATE_TRANSITION",
"creationTime": "2023-03-19T20:13:07.563623+02:00",
"errorMessage": null,
"durationInMs": null,
"stateTransition": "CLOSED_TO_OPEN"
}

審視最後一個事件的 <em >stateTransition</em > 屬性,斷路器處於 <em >OPEN</em > 狀態。新的調用嘗試會引發 <em >CallNotPermittedException</em > 異常,進而觸發 <em >CircuitBreakerOnCallNotPermittedEvent</em > 事件:

{
    "circuitBreakerName": "externalService",
    "type": "NOT_PERMITTED",
    "creationTime": "2023-03-22T16:50:11.897977+02:00",
    "errorMessage": null,
    "durationInMs": null,
    "stateTransition": null
}

在配置的 waitDuration 持續時間結束後,熔斷器將過渡到中間狀態 OPEN_TO_HALF_OPEN,通過 CircuitBreakerOnStateTransitionEvent 再次發出信號:

{
    "circuitBreakerName": "externalService",
    "type": "STATE_TRANSITION",
    "creationTime": "2023-03-22T16:50:14.787381+02:00",
    "errorMessage": null,
    "durationInMs": null,
    "stateTransition": "OPEN_TO_HALF_OPEN"
}

<em >OPEN_TO_HALF_OPEN</em > 狀態下,如果配置的 <em >minimumNumberOfCalls</em > 成功,則再次觸發 <em >CircuitBreakerOnStateTransitionEvent</em >,從而返回到 <em >OPEN</em > 狀態:

{
    "circuitBreakerName": "externalService",
    "type": "STATE_TRANSITION",
    "creationTime": "2023-03-22T17:48:45.931978+02:00",
    "errorMessage": null,
    "durationInMs": null,
    "stateTransition": "HALF_OPEN_TO_CLOSED"
}

與斷路器相關的事件提供對實例性能和請求處理方式的見解。因此,我們可以通過分析斷路器事件來識別潛在問題並跟蹤性能指標。

3.3. 消費發出的 CircuitBreakerEvent 事件

雖然 actuator 端點提供了一種方便的方式來檢查發出的事件,但 Resilience4j 也允許我們通過編程方式消費 CircuitBreakerEvent 事件。 這使得能夠與日誌框架、監控管道或自定義警報系統進行直接集成。

每個 CircuitBreaker 實例都暴露了一個 EventPublisher,該 EventPublisher 提供了針對每種事件類型的專用訂閲方法。 在應用程序啓動期間,我們可以註冊監聽器:

@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;

@PostConstruct
public void registerEventConsumers() {
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("externalService");

    circuitBreaker.getEventPublisher()
      .onSuccess(event -> log.info("Success: {}", event))
      .onError(event -> log.error("Error: {}", event))
      .onIgnoredError(event -> log.warn("Ignored error: {}", event))
      .onCallNotPermitted(event -> log.warn("Call not permitted: {}", event))
      .onFailureRateExceeded(event -> log.error("Failure rate exceeded: {}", event))
      .onSlowCallRateExceeded(event -> log.error("Slow call rate exceeded: {}", event))
      .onStateTransition(event -> log.info("State transition: {}", event.getStateTransition()))
      .onReset(event -> log.info("Circuit breaker reset: {}", event));
}

可用的鈎子覆蓋了斷路器整個生命週期的各個階段,從成功的調用到重置及之後:

  • onSuccess():在調用成功完成後觸發
  • onError():在調用失敗時觸發
  • onIgnoredError():當異常發生但未被故障記錄邏輯忽略時引發
  • onCallNotPermitted():當調用由於斷路器處於OPEN狀態而被拒絕時發出
  • onFailureRateExceeded():配置的故障率閾值超過時發佈
  • onSlowCallRateExceeded():慢調用率超過閾值時發佈
  • onStateTransition():當斷路器在狀態之間轉換時(例如,CLOSED → OPEN)發出信號
  • onReset():當斷路器重置為初始狀態時發出

這些事件共同提供了一個關於斷路器行為的詳細視圖,從而可以實現實時反應和跟蹤長期穩定性趨勢。

4. 嘗試重試

如果操作失敗,您可以嘗試重試。 這通常發生在網絡連接不穩定、服務器繁忙或其他臨時問題時。 重新嘗試操作可能會成功。

4.1. 配置

對於我們的 /api/retry 端點,我們將使用以下配置創建一個 Retry 實例:

resilience4j.retry:
  configs:
    default:
      maxAttempts: 3
      waitDuration: 100
  instances:
    externalService:
      baseConfig: default

4.2. 事件

讓我們考察重試模式在執行器端點下列出的事件內容:

http://localhost:8080/actuator/retryevents

當調用失敗時,將根據配置進行重試:

@Test
void testRetryEvents()throws Exception {
    EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
      .willReturn(serverError()));
    ResponseEntity<String> response = restTemplate.getForEntity("/api/retry", String.class);
     
    ...
}

Consequently, 對於每次重試嘗試,都會發出一個 RetryOnErrorEvent,並由 Retry 實例根據其配置安排下一次重試。 如你所見,該事件具有 numberOfAttempts 計數器字段:

{
"retryName": "retryApi",
"type": "RETRY",
"creationTime": "2023-03-19T22:57:51.458811+02:00",
"errorMessage": "org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 Server Error: \"{\"error\": \"Internal Server Error\"}\"",
"numberOfAttempts": 1
}

因此,當配置的重試嘗試次數耗盡後,Retry 實例會發佈一個 RetryOnFailedEvent,同時也會允許底層異常傳播:

{
"retryName": "retryApi",
"type": "ERROR",
"creationTime": "2023-03-19T23:30:11.440423+02:00",
"errorMessage": "org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 Server Error: \"{\"error\": \"Internal Server Error\"}\"",
"numberOfAttempts": 3
}

Retry 利用這些事件來確定是否安排重試或放棄並報告失敗,從而指示當前流程的狀態。因此,監控這些事件可以幫助優化 Retry 配置,以獲得最大的效益。

5. 時間限制器

時間限制器用於限制執行某個操作或任務所允許的時間。這在各種場景下都非常有用,例如:

  • 防止惡意行為: 限制用户在特定操作中花費過多的時間,以防止惡意攻擊或濫用。
  • 優化性能: 限制長時間運行的任務,以避免對系統資源造成過度消耗。
  • 模擬真實環境: 在測試和開發過程中,模擬真實環境中的時間限制。

以下是一些常見的實現時間限制器的技術:

  • 定時器: 使用定時器來跟蹤任務的執行時間,並在達到限制時間時終止任務。
  • 信號處理: 使用信號處理機制來中斷任務的執行。
  • 線程池: 通過限制線程池中的任務數量來控制任務的執行時間。
# 示例:使用 threading 模塊實現時間限制器
import threading
import time

def task(name):
    print(f"Task {name} started")
    time.sleep(5)  # 模擬任務執行
    print(f"Task {name} finished")

if __name__ == "__main__":
    thread = threading.Thread(target=task, args=("MyTask",))
    thread.start()
    thread.join()

5.1. 配置

我們實例上定義的 Time Limiter 配置被 api/time-limiter 接口所使用:

resilience4j.timelimiter:
  configs:
    default:
      cancelRunningFuture: true
      timeoutDuration: 2s
  instances:
    externalService:
      baseConfig: default

5.2. 事件

Time Limiter 事件在端點處列出:

http://localhost:8080/actuator/timelimiterevents

時間限制事件提供關於操作狀態的信息,實例通過響應這些事件來決定是否允許請求完成或在請求超時超過配置值時取消該請求。

例如,如果一個調用在配置的時間限制內執行,則會發出 TimeLimiterOnSuccessEvent 事件:

{
    "timeLimiterName":"externalService",
    "type":"SUCCESS",
    "creationTime":"2023-03-20T20:48:43.089529+02:00"
}

另一方面,當調用在時間限制內失敗時,會發生TimeLimiterOnErrorEvent

{
    "timeLimiterName":"externalService",
    "type":"ERROR",
    "creationTime":"2023-03-20T20:49:12.089537+02:00"
}

由於我們的 /api/time-limiter 接口實現的時間延遲超過了 timeoutDuration 配置,會導致調用超時。因此,它會遇到 TimeoutException 異常,並觸發 TimeLimiterOnErrorEvent 事件:

@Test
void testTimeLimiterEvents() throws Exception {
    EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
      .willReturn(ok()));
    ResponseEntity<String> response = restTemplate.getForEntity("/api/time-limiter", String.class);
        
    ...
}
{
    "timeLimiterName":"externalService",
    "type":"TIMEOUT",
    "creationTime":"2023-03-20T19:32:38.733874+02:00"
}

監測 Time Limiter 事件使我們能夠跟蹤請求狀態並解決與超時相關的故障,從而幫助我們優化響應時間。

6. 隔牆 (Bulkhead)

A bulkhead is a vertical partition within a ship or aircraft that provides structural support and division. It’s a critical component in maintaining the integrity of the vessel and separating different compartments. Bulkheads are typically constructed from steel, but can also be made from other materials depending on the specific requirements of the project.

關鍵概念:

  • 結構支撐: 隔牆的主要作用是提供結構支撐,增強船體或飛機的整體強度。
  • 艙室分隔: 隔牆將船體或飛機劃分為多個獨立的艙室,以防止水或火的蔓延。
  • 鋼結構: 隔牆通常由鋼材製成,但也可以根據具體需求使用其他材料。

代碼示例 (C++):

// 這是一個示例代碼,用於演示隔牆的概念。
// 隔牆用於將不同的功能模塊分隔開來,提高代碼的可維護性。
class Module {
  // 模塊的定義
};

class Wall {
  // 牆的定義
};

6.1. 配置

讓我們使用配置來創建我們的 Bulkhead 實例:

resilience4j.bulkhead:
  configs:
    default:
      max-concurrent-calls: 3
      max-wait-duration: 1
  instances:
    externalService:
      baseConfig: default

6.2. 事件

我們可以通過 Bulkhead 模式的執行器端點觀察使用的具體事件。

http://localhost:8080/actuator/bulkheadevents

讓我們來看一下當模式在超過允許的併發限制的情況下發出的事件:

@Test
void testBulkheadEvents() throws Exception {
    EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(ok()));
    Map<Integer, Integer> responseStatusCount = new ConcurrentHashMap<>();
    ExecutorService executorService = Executors.newFixedThreadPool(5);

    List<Callable<Integer>> tasks = new ArrayList<>();
    IntStream.rangeClosed(1, 5)
      .forEach(
        i ->
          tasks.add(
            () -> {
            ResponseEntity<String> response =
              restTemplate.getForEntity("/api/bulkhead", String.class);
            return response.getStatusCodeValue();
            }));

    List<Future<Integer>> futures = executorService.invokeAll(tasks);
    for (Future<Integer> future : futures) {
      int statusCode = future.get();
      responseStatusCount.merge(statusCode, 1, Integer::sum);
    }
    ...
}

bulkhead 機制通過配置允許或拒絕調用來響應事件。例如,在允許的併發限制內允許調用時,它會消耗一個可用的槽位併發出 BulkheadOnCallPermittedEvent

{
    "bulkheadName":"externalService",
    "type":"CALL_PERMITTED",
    "creationTime":"2023-03-20T14:10:52.417063+02:00"
}

當配置的併發限制達到時,進一步的併發調用會被 Bulkhead 實例拒絕,並拋出 BulkheadFullException,從而觸發 BulkheadOnCallRejectedEvent

{
    "bulkheadName":"externalService",
    "type":"CALL_REJECTED",
    "creationTime":"2023-03-20T14:10:52.419099+02:00"
}

當調用執行完成後,無論成功還是出錯,槽位將被釋放,並觸發 BulkheadOnCallFinishedEvent

{
    "bulkheadName":"externalService",
    "type":"CALL_FINISHED",
    "creationTime":"2023-03-20T14:10:52.500715+02:00"
}

觀察圍板事件有助於確保資源的隔離並維持在高負載或故障期間的穩定性能。 類似地,通過跟蹤允許和拒絕的調用數量,並相應地調整圍板配置,我們可以更好地平衡服務可用性和資源保護。

7. 速率限制器

速率限制器(Rate Limiter)是一種用於限制客户端或用户在特定時間段內請求資源數量的技術。它通過跟蹤請求數量並根據預定義的策略進行限制,從而防止濫用、保護服務器資源和提高系統穩定性。

工作原理:

速率限制器通常使用以下機制來實施限制:

  • 令牌桶算法 (Token Bucket Algorithm): 想象一個裝滿令牌的桶。每個請求消耗一個令牌。令牌以恆定速率填充桶。當桶耗盡時,後續請求將被延遲或拒絕。
  • 漏桶算法 (Leaky Bucket Algorithm): 請求進入一個桶,桶以恆定速率流出。如果桶滿了,新的請求將被拒絕。
  • 滑動窗口計數器 (Sliding Window Counter): 跟蹤一定時間窗口內請求數量。每個請求會增加計數器,計數器達到上限時,後續請求將被延遲或拒絕。

配置參數:

  • Limit (限制): 允許在指定時間段內執行的最大請求數量。
  • Time Window (時間窗口): 定義限制的有效時間段。
  • Granularity (粒度): 定義限制的粒度,例如按用户、IP 地址或 API 密鑰進行限制。

示例 (JavaScript):

// 示例:使用令牌桶算法限制請求速率
const tokenBucket = {
  tokens: 10, // 初始令牌數量
  capacity: 10, // 令牌桶容量
  tick: function() {
    this.tokens = this.capacity; // 令牌以恆定速率填充桶
  },
  consume: function(tokens) {
    this.tokens -= tokens;
    if (this.tokens < 0) {
      this.tokens = 0;
    }
  }
};

// 模擬請求
function makeRequest() {
  if (tokenBucket.tokens > 0) {
    tokenBucket.consume(1);
    console.log("請求成功!");
  } else {
    console.log("請求被限制!");
  }
}

// 模擬時間流逝
setInterval(makeRequest, 1000); // 每秒一個請求

7.1. 配置

我們將基於以下配置,為 api/rate-limiter</em/> 終點創建 Rate Limiter 實例:

resilience4j.ratelimiter:
  configs:
    default:
      limit-for-period: 5
      limit-refresh-period: 60s
      timeout-duration: 0s
      allow-health-indicator-to-fail: true
      subscribe-for-events: true
      event-consumer-buffer-size: 50
  instances:
    externalService:
      baseConfig: default

7.2. 事件

對於速率限制器模式,可以在以下端點下找到事件列表:

http://localhost:8080/actuator/ratelimiterevents

讓我們檢查當同時調用 /api/rate-limiter</em/> 接口,超過配置的速率限制時產生的事件:

@Test
void testRateLimiterEvents() throws Exception {
    EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external")
      .willReturn(ok()));

    IntStream.rangeClosed(1, 50)
      .forEach(i -> {
        ResponseEntity<String> response = restTemplate.getForEntity("/api/rate-limiter", String.class);
        int statusCode = response.getStatusCodeValue();
        responseStatusCount.put(statusCode, responseStatusCount.getOrDefault(statusCode, 0) + 1);
      });
        
    ...
}

最初,每個請求在成功獲取令牌之前,在令牌桶中獲取令牌幾次後就會達到速率限制。因此,該庫會觸發 RateLimiterOnSuccessEvent

{
    "rateLimiterName":"externalService",
    "type":"SUCCESSFUL_ACQUIRE",
    "creationTime":"2023-03-20T10:55:19.314306+02:00"
}

一旦配置的 limit-refresh-period 令牌耗盡,進一步的調用將導致 RequestNotPermitted 異常,從而觸發 RateLimiterOnFailureEvent

{
    "rateLimiterName":"externalService",
    "type":"FAILED_ACQUIRE",
    "creationTime":"2023-03-20T12:48:28.623726+02:00"
}

速率限制事件允許監控端點處理請求的速率。通過跟蹤成功的/未能獲取事件的數量,我們可以評估速率限制是否合適,確保客户端獲得良好的服務並保護資源。

8. 結論

在本文中,我們瞭解到 Resilience4j 發出的用於 Circuit Breaker、Rate Limiter、Bulkhead 和 Time Limiter 模式的事件,以及訪問這些事件的端點。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.