Rate Limiting in Spring Cloud Netflix Zuul – Spring Cloud Netflix中的速率限制 Zuul

最后修改: 2018年 11月 4日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Spring Cloud Netflix Zuul is an open source gateway that wraps Netflix Zuul. It adds some specific features for Spring Boot applications. Unfortunately, rate limiting is not provided out of the box.

Spring Cloud Netflix Zuul是一个开源的网关,它封装了Netflix Zuul。它为Spring Boot应用程序添加了一些特定的功能。遗憾的是,没有提供开箱即用的速率限制。

In this tutorial, we will explore Spring Cloud Zuul RateLimit which adds support for rate limiting requests.

在本教程中,我们将探讨Spring Cloud Zuul RateLimit,它增加了对速率限制请求的支持。

2. Maven Configuration

2.Maven配置

In addition to the Spring Cloud Netflix Zuul dependency, we need to add Spring Cloud Zuul RateLimit to our application’s pom.xml:

除了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. Example Controller

3.控制器实例

Firstly, let’s create a couple of REST endpoints on which we will apply the rate limits.

首先,让我们创建几个REST端点,我们将在这些端点上应用速率限制。

Below is a simple Spring Controller class with two endpoints:

下面是一个简单的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?");
    }
}

As we can see, there is no code specific to rate limit the endpoints. This is because we’ll configure that in our Zuul properties within the application.yml file. Thus, keeping our code decoupled.

我们可以看到,没有专门的代码来限制端点的速率。这是因为我们将在application.yml文件中的Zuul属性中进行配置。因此,保持我们的代码解耦。

4. Zuul Properties

4.Zuul Properties

Secondly, let’s add the following Zuul properties in our application.yml file:

其次,让我们在我们的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

Under zuul.routes we provide the endpoint details. And under zuul.ratelimit.policy-list, we provide the rate limit configurations for our endpoints. The limit property specifies the number of times the endpoint can be called within the refresh-interval.

zuul.routes下,我们提供端点的详细信息。而在zuul.ratelimit.policy-list下,我们为我们的端点提供速率限制配置。limit属性规定了在refresh-interval内可以调用端点的次数。

As we can see, we added a rate limit of 5 requests per 60 seconds for the serviceSimple endpoint. In contrast, serviceAdvanced has a rate limit of 1 request per 2 seconds.

我们可以看到,我们为serviceSimple端点添加了每60秒5个请求的速率限制。相比之下,serviceAdvanced的速率限制为每2秒1个请求。

The type configuration specifies which rate limit approach we want to follow. Here are the possible values:

type配置指定了我们要遵循的速率限制方法。以下是可能的值。

  • origin – rate limit based on the user origin request
  • url – rate limit based on the request path of the downstream service
  • user – rate limit based on the authenticated username or ‘anonymous’
  • No value – acts as a global configuration per service. To use this approach just don’t set param ‘type’

5. Testing the Rate Limit

5.测试速率限制

5.1. Request Within the Rate Limit

5.1.在费率限额内提出要求

Next, let’s test the rate limit:

接下来,让我们测试一下速率限制。

@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)))
    );
}

Here we make a single call to the endpoint /greeting/simple. The request is successful since it is within the rate limit.

这里我们对端点/greeting/simple进行了一次调用。该请求是成功的,因为它在速率限制之内。

Another key point is that with each response we get back headers providing us with further information on the rate limit. For above request, we would get following headers:

另一个关键点是,在每个响应中,我们都会得到头信息,为我们提供关于速率限制的进一步信息。对于上述请求,我们会得到以下头信息。

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

In other words:

换句话说。

  • X-RateLimit-Limit-[key]: the limit configured for the endpoint
  • X-RateLimit-Remaining-[key]: the remaining number of attempts to call the endpoint
  • X-RateLimit-Reset-[key]: the remaining number of milliseconds of the refresh-interval configured for the endpoint

In addition, if we immediately fire the same endpoint again, we could get:

此外,如果我们立即再次启动同一个端点,我们可以得到。

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

Notice the decreased remaining number of attempts and remaining number of milliseconds.

注意到剩余的尝试次数和剩余的毫秒数减少了

5.2. Request Exceeding the Rate Limit

5.2.超过费率限制的要求

Let’s see what happens when we exceed the rate limit:

让我们看看当我们超过速率限制时会发生什么。

@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());
}

Here we call the endpoint /greeting/advanced twice in quick successions. Since we have configured rate limit as one request per 2 seconds, the second call will fail. As a result, the error code 429 (Too Many Requests) is returned to the client.

在这里,我们快速连续调用端点/greeting/advanced两次。由于我们已经将速率限制配置为每2秒一个请求,第二次调用将失败。因此,错误代码429(太多的请求)被返回给客户端。

Below are the headers returned when the rate limit is reached:

以下是达到速率限制时返回的标题。

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

After that, we sleep for 2 seconds. This is the refresh-interval configured for the endpoint. Finally, we fire the endpoint again and get a successful response.

之后,我们会休眠2秒。这是为该端点配置的refresh-interval。最后,我们再次启动端点并得到一个成功的响应。

6. Custom Key Generator

6.自定义密钥生成器

We can customize the keys sent in the response header using a custom key generator. This is useful because the application might need to control the key strategy beyond the options offered by the type property.

我们可以使用自定义密钥生成器来定制在响应头中发送的密钥。这很有用,因为应用程序可能需要控制密钥策略,而不是由type属性提供的选项。

For instance, this can be done by creating a custom RateLimitKeyGenerator implementation. We can add further qualifiers or something entirely different:

例如,这可以通过创建一个自定义的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();
        }
    };
}

The code above appends the REST method name to the key. For example:

上面的代码将REST方法的名称附加到键上。比如说。

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

Another key point is that the RateLimitKeyGenerator bean will be automatically configured by spring-cloud-zuul-ratelimit.

另一个关键点是,RateLimitKeyGenerator Bean将由spring-cloud-zuul-ratelimit自动配置。

7. Custom Error Handling

7.自定义错误处理

The framework supports various implementations for rate limit data storage. For instance, Spring Data JPA and Redis are provided. By default, failures are just logged as errors using the DefaultRateLimiterErrorHandler class.

该框架支持速率限制数据存储的各种实现。例如,提供Spring Data JPA和Redis。默认情况下,失败只是使用DefaultRateLimiterErrorHandler类记录为错误

When we need to handle the errors differently, we can define a custom RateLimiterErrorHandler bean:

当我们需要以不同方式处理错误时,我们可以定义一个自定义的RateLimiterErrorHandlerbean。

@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
        }
    };
}

Similar to the RateLimitKeyGenerator bean, the RateLimiterErrorHandler bean will also be automatically configured.

RateLimitKeyGenerator bean类似,RateLimiterErrorHandler bean也将被自动配置。

8. Conclusion

8.结论

In this article, we saw how to rate limit APIs using Spring Cloud Netflix Zuul and Spring Cloud Zuul RateLimit.

在这篇文章中,我们看到了如何使用Spring Cloud Netflix Zuul和Spring Cloud Zuul RateLimit进行速率限制API。

As always, the complete code for this article can be found over on GitHub.

像往常一样,本文的完整代码可以在GitHub上找到over