Spring Cloud Netflix Zuul中的速率限制
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類:
@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?");
}
}
如我們所見,沒有特定的代碼可以對端點進行速率限制。這是因為我們將在application.yml
文件的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
端點添加了每60秒5個請求的速率限制。相反, serviceAdvanced
的速率限制為每2秒1個請求。
type
配置指定我們要遵循的速率限制方法。以下是可能的值:
-
origin
–基於用戶原點請求的速率限制 -
url
–基於下游服務請求路徑的速率限制 -
user
–基於經過驗證的用戶名或“匿名”的速率限制 - 沒有價值–充當每個服務的全局配置。要使用這種方法,只是不要設置param'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));
assertEquals("60000", headers.getFirst(HEADER_RESET + key));
}
在這裡,我們對端點/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]:
為端點配置的limit
-
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秒鐘。這是為端點配置的refresh-interval
。最後,我們再次觸發端點並獲得成功的響應。
6.**自定義密鑰生成器**
我們可以使用自定義密鑰生成器自定義在響應標頭中發送的密鑰。這很有用,因為應用程序可能需要控制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進行限價。
與往常一樣,可以在GitHub上找到本文的完整代碼。