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 進行限速。