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上找到本文的完整代碼。