知識庫 / Spring / Spring Cloud RSS 訂閱

Spring Cloud Netflix Zuul 限流技術詳解

Spring Cloud
HongKong
6
01:37 PM · Dec 06 ,2025

1. 簡介

Spring Cloud Netflix Zuul 是一個開源網關,它封裝了 Netflix Zuul。它為 Spring Boot 應用程序添加了一些特定功能。不幸的是,內置的速率限制功能沒有提供。

在本教程中,我們將探索 Spring Cloud Zuul RateLimit,它為請求添加了速率限制的支持。

2. Maven 配置

除了 Spring Cloud Netflix Zuul 依賴之外,還需要將 Spring Cloud Zuul RateLimit 添加到應用程序的 pom.xml 中:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

3. 示例控制器

首先,讓我們在其中應用速率限制上創建一些 REST 端點。

以下是一個簡單的 Spring 控制器類,其中包含兩個端點:

@Controller
@RequestMapping("/greeting")
public class GreetingController {

    @GetMapping("/simple")
    public ResponseEntity<String> getSimple() {
        return ResponseEntity.ok("Hi!");
    }

    @GetMapping("/advanced")
    public ResponseEntity<String> getAdvanced() {
        return ResponseEntity.ok("Hello, how you doing?");
    }
}

正如我們所見,沒有針對限制端點的特定代碼。這是因為我們將配置在我們的 Zuul 屬性文件中。因此,我們保持代碼解耦。

4. Zuul 屬性

其次,我們在 application.yml 文件中添加以下 Zuul 屬性:

zuul:
  routes:
    serviceSimple:
      path: /greeting/simple
      url: forward:/
    serviceAdvanced:
      path: /greeting/advanced
      url: forward:/
  ratelimit:
    enabled: true
    repository: JPA
    policy-list:
      serviceSimple:
        - limit: 5
          refresh-interval: 60
          type:
            - origin
      serviceAdvanced:
        - limit: 1
          refresh-interval: 2
          type:
            - origin
  strip-prefix: true

zuul.routes 下,我們提供端點的詳細信息。 此外,在 zuul.ratelimit.policy-list 下,我們提供端點的速率限制配置。 limit 屬性指定端點在 refresh-interval 內可以被調用的次數。

如我們所見,我們為 serviceSimple 端點設置了 5 個請求的速率限制,每 60 秒一次。 相比之下,serviceAdvanced 端點的速率限制為 1 個請求,每 2 秒一次。

type 配置指定我們希望遵循的速率限制方法。 以下是可能的選項:

  • origin – 基於用户請求源的速率限制
  • url – 基於下游服務請求路徑的速率限制
  • user – 基於身份驗證用户名或“anonymous”的速率限制
  • 無值 – 作為每個服務的全局配置。 要使用此方法,只需不要設置參數 ‘type’

5. 測試速率限制

在實施速率限制策略後,務必進行徹底的測試,以確保其正常運行並不會對應用程序的性能產生負面影響。以下是一些建議的測試方法:

  • 負載測試: 使用工具模擬大量併發請求,以確定系統在不同負載下的性能表現。
  • 邊緣測試: 檢查速率限制是否在預期範圍內,並且在正常流量下不會導致用户體驗問題。
  • 邊界測試: 嘗試超出速率限制的請求,以驗證限制的生效情況。
  • 錯誤處理測試: 驗證系統是否正確處理超過速率限制的請求,並返回適當的錯誤代碼或消息。
  • 監控和日誌記錄: 啓用監控和日誌記錄,以便跟蹤請求速率、錯誤率和系統資源使用情況。

通過這些測試,您可以確保您的速率限制策略既能保護您的應用程序,又能提供良好的用户體驗。

5.1. 請求速率限制

接下來,讓我們測試速率限制:

@Test
public void whenRequestNotExceedingCapacity_thenReturnOkResponse() {
    ResponseEntity<String> response = restTemplate.getForEntity(SIMPLE_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application_serviceSimple_127.0.0.1";

    assertEquals("5", headers.getFirst(HEADER_LIMIT + key));
    assertEquals("4", headers.getFirst(HEADER_REMAINING + key));
    assertThat(
      parseInt(headers.getFirst(HEADER_RESET + key)),
      is(both(greaterThanOrEqualTo(0)).and(lessThanOrEqualTo(60000)))
    );
}

我們通過一次調用端點 greeting/simple。由於請求在速率限制內,因此請求成功。

另一個關鍵點是,在每次收到響應時,我們獲得的信息頭,這些信息頭提供了關於速率限制的更多信息。 對於上述請求,我們將收到以下信息頭:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 4
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 60000

換句話説:

  • X-RateLimit-Limit-[key]: 該端點的配置限制
  • X-RateLimit-Remaining-[key]: 剩餘的調用該端點嘗試次數
  • X-RateLimit-Reset-[key]: 該端點的refresh-interval剩餘毫秒數

此外,如果我們在立即再次調用同一個端點,我們可能會得到:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1: 5
X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 3
X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 57031

請注意剩餘嘗試次數和剩餘毫秒數已減少。

5.2. 請求超出速率限制

讓我們看看當請求超出速率限制時會發生什麼:

@Test
public void whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException {
    ResponseEntity<String> response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());
    
    for (int i = 0; i < 2; i++) {
        response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    }

    assertEquals(TOO_MANY_REQUESTS, response.getStatusCode());

    HttpHeaders headers = response.getHeaders();
    String key = "rate-limit-application_serviceAdvanced_127.0.0.1";

    assertEquals("1", headers.getFirst(HEADER_LIMIT + key));
    assertEquals("0", headers.getFirst(HEADER_REMAINING + key));
    assertNotEquals("2000", headers.getFirst(HEADER_RESET + key));

    TimeUnit.SECONDS.sleep(2);

    response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);
    assertEquals(OK, response.getStatusCode());
}

我們在這裏調用端點 /greeting/advanced 兩次,快速連續調用。由於我們已配置了每 2 秒一次的請求限制,第二次調用將會失敗。 結果,錯誤碼 429 (Too Many Requests) 被返回給客户端。

以下是請求限制達到時返回的頭部信息:

X-RateLimit-Limit-rate-limit-application_serviceAdvanced_127.0.0.1: 1
X-RateLimit-Remaining-rate-limit-application_serviceAdvanced_127.0.0.1: 0
X-RateLimit-Reset-rate-limit-application_serviceAdvanced_127.0.0.1: 268

之後,我們等待2秒鐘進行休眠。這是配置在端點上的 刷新間隔。最後,我們再次觸發端點並獲得成功的響應。

自定義鍵生成器

我們可以使用自定義鍵生成器來定製響應頭中發送的鍵。這在應用程序可能需要控制密鑰策略,超出 type 屬性提供的選項時非常有用。

例如,可以通過創建自定義 RateLimitKeyGenerator 實現來完成。我們可以添加額外的限定符或完全不同的內容:

@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties, 
  RateLimitUtils rateLimitUtils) {
    return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
        @Override
        public String key(HttpServletRequest request, Route route, 
          RateLimitProperties.Policy policy) {
            return super.key(request, route, policy) + "_" + request.getMethod();
        }
    };
}

代碼上方將 REST 方法名稱追加到鍵中。例如:

X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5

另一個關鍵點是,RateLimitKeyGenerator Bean 將會自動由 spring-cloud-zuul-ratelimit 配置。

7. 自定義錯誤處理

該框架支持多種速率限制數據存儲實現。例如,Spring Data JPA 和 Redis 已提供。默認情況下,錯誤會被記錄為錯誤,使用 DefaultRateLimiterErrorHandler 類。

當我們需要以不同的方式處理錯誤時,可以定義一個自定義 RateLimiterErrorHandler Bean:

@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
    return new DefaultRateLimiterErrorHandler() {
        @Override
        public void handleSaveError(String key, Exception e) {
            // implementation
        }

        @Override
        public void handleFetchError(String key, Exception e) {
            // implementation
        }

        @Override
        public void handleError(String msg, Exception e) {
            // implementation
        }
    };
}

RateLimitKeyGenerator Bean 類似,RateLimiterErrorHandler Bean 也會自動配置。

8. 結論

在本文中,我們學習瞭如何使用 Spring Cloud Netflix Zuul 和 Spring Cloud Zuul RateLimit 對 API 進行限速。

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

發佈 評論

Some HTML is okay.