查看原文
其他

Spring Cloud Netflix Zuul中的速率限制

shirehappy SpringForAll社区 2020-10-17

原文链接:https://www.baeldung.com/spring-cloud-zuul-rate-limit

作者:Ganesh Pagade

译者:shirehappy

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。

  1. <dependency>

  2.    <groupId>org.springframework.cloud</groupId>

  3.    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>

  4. </dependency>

  5. <dependency>

  6.    <groupId>com.marcosbarbero.cloud</groupId>

  7.    <artifactId>spring-cloud-zuul-ratelimit</artifactId>

  8.    <version>2.2.0.RELEASE</version>

  9. </dependency>

3.Controller例子

首先,我们在将要应用速率限制的地方创建一些REST端点。 下面是一个简单的有两个端点的Spring Controller类。

  1. @Controller

  2. @RequestMapping("/greeting")

  3. public class GreetingController {

  4.    @GetMapping("/simple")

  5.    public ResponseEntity<String> getSimple() {

  6.        return ResponseEntity.ok("Hi!");

  7.    }

  8.    @GetMapping("/advanced")

  9.    public ResponseEntity<String> getAdvanced() {

  10.        return ResponseEntity.ok("Hello, how you doing?");

  11.    }

  12. }

就像我们看到的,在速率限制的端点上没有特别的代码。这是因为我们将要配置这些速率限制在 我们的Zuul 配置文件application.yml上。因此,保持我们的代码解耦。

4.Zuul配置

第二步,我们增加如下Zuul 配置到我们的application.yml文件上。

  1. zuul:

  2.  routes:

  3.    serviceSimple:

  4.      path: /greeting/simple

  5.      url: forward:/

  6.    serviceAdvanced:

  7.      path: /greeting/advanced

  8.      url: forward:/

  9.  ratelimit:

  10.    enabled: true

  11.    repository: JPA

  12.    policy-list:

  13.      serviceSimple:

  14.        - limit: 5

  15.          refresh-interval: 60

  16.          type:

  17.            - origin

  18.      serviceAdvanced:

  19.        - limit: 1

  20.          refresh-interval: 2

  21.          type:

  22.            - origin

  23.  strip-prefix: true

在zuul.routes下,我们提供了端点详情。在zuul.ratelimit.policy-list下,我们为端点提供了 速率限制配置。limit属性指定了端点在refresh-interval区间能被调用的次数。

就像我们看到的,我们为serviceSimple 端点增加了每60秒5次请求的速率限制。相反的, serviceAdvanced增加了每2秒1次请求的速率限制。

type配置制定了我们想要的速率限制方法。下面是一些可能的值:

  • origin-基于用户原始请求的速率限制

  • url-基于下游服务的请求路径的速率限制

  • user-基于认证用户名或匿名用户的速率限制

  • 空值-每一个服务使用一个全局配置,要使用这个方式只需要不设置参数‘type’

5.测试速率限制

5.1.请求在限额内

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

  1. @Test

  2. public void whenRequestNotExceedingCapacity_thenReturnOkResponse() {

  3.    ResponseEntity<String> response = restTemplate.getForEntity(SIMPLE_GREETING, String.class);

  4.    assertEquals(OK, response.getStatusCode());

  5.    HttpHeaders headers = response.getHeaders();

  6.    String key = "rate-limit-application_serviceSimple_127.0.0.1";

  7.    assertEquals("5", headers.getFirst(HEADER_LIMIT + key));

  8.    assertEquals("4", headers.getFirst(HEADER_REMAINING + key));

  9.    assertEquals("60000", headers.getFirst(HEADER_RESET + key));    

  10. }

这里我们创建一个到端点/greeting/simple的请求。这个请求在速率限制内是成功的。

另一个关键点是我们获得的每一个响应的响应头能提供我们更多的速率限制的信息。 对上面的请求,我们将得到如下的响应头。

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

  2. X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 4

  3. X-RateLimit-Reset-rate-limit-application_serviceSimple_127.0.0.1: 60000

换句话说:X-RateLimit-Limit-[key]: 端点的限制配置 X-RateLimit-Remaining-[key]: 调用端点的剩余尝试次数 *X-RateLimit-Reset-[key]: 端点配置刷新间隔的剩余毫秒次数

另外,如果我们立即再次调用同样的端点,我们将得到

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

  2. X-RateLimit-Remaining-rate-limit-application_serviceSimple_127.0.0.1: 3

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

注意剩余调用次数和剩余调用毫秒时间已经减少。

5.2.请求在限额外

让我们看下超过限制速率将发生什么:

  1. @Test

  2. public void whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException {

  3.    ResponseEntity<String> response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);

  4.    assertEquals(OK, response.getStatusCode());

  5.    for (int i = 0; i < 2; i++) {

  6.        response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);

  7.    }

  8.    assertEquals(TOO_MANY_REQUESTS, response.getStatusCode());

  9.    HttpHeaders headers = response.getHeaders();

  10.    String key = "rate-limit-application_serviceAdvanced_127.0.0.1";

  11.    assertEquals("1", headers.getFirst(HEADER_LIMIT + key));

  12.    assertEquals("0", headers.getFirst(HEADER_REMAINING + key));

  13.    assertNotEquals("2000", headers.getFirst(HEADER_RESET + key));

  14.    TimeUnit.SECONDS.sleep(2);

  15.    response = this.restTemplate.getForEntity(ADVANCED_GREETING, String.class);

  16.    assertEquals(OK, response.getStatusCode());

  17. }

这里我们快速连续调用/greeting/advanced 端点两次。因为我们配置了2秒只能1次请求的速率限制, 第二次调用将会失败。错误码429(请求频繁)将作为结果返回给客户端。

下面是速率限制达到的时候返回的响应头:

  1. X-RateLimit-Limit-rate-limit-application_serviceAdvanced_127.0.0.1: 1

  2. X-RateLimit-Remaining-rate-limit-application_serviceAdvanced_127.0.0.1: 0

  3. X-RateLimit-Reset-rate-limit-application_serviceAdvanced_127.0.0.1: 268

之后,我们休眠2秒,这是为端点刷新区间配置。最后,我们再次调用端点并能获得成功的响应。

6.定制秘钥生成器

我们能使用一个秘钥生成器来在响应头中定制秘钥发送。这是很有用的,因为应用可能需要除了可选的type属性之外的控制秘钥策略。

举例说明,通过创建一个定制的RateLimitKeyGenerator 实现。我们能添加更多的限定符或一些完全不一样的事情:

  1. @Bean

  2. public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,

  3.  RateLimitUtils rateLimitUtils) {

  4.    return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {

  5.        @Override

  6.        public String key(HttpServletRequest request, Route route,

  7.          RateLimitProperties.Policy policy) {

  8.            return super.key(request, route, policy) + "_" + request.getMethod();

  9.        }

  10.    };

  11. }

上面的代码为秘钥追加了REST方法名。举例:

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

另一个关键点是RateLimitKeyGenerator对象将被spring-cloud-zuul-ratelimit自动配置.

7.个性化错误处理

框架支持针对速率限制数据存储的各种各样的实现。举例来说:Spring Data JPA 和 Redis 都有提供。 默认的,失败将通过DefaultRateLimiterErrorHandler类作为错误记录下来

当我们需要对错误进行不同处理时,我们能自定义RateLimiterErrorHandler 对象。

  1. @Bean

  2. public RateLimiterErrorHandler rateLimitErrorHandler() {

  3.    return new DefaultRateLimiterErrorHandler() {

  4.        @Override

  5.        public void handleSaveError(String key, Exception e) {

  6.            // implementation

  7.        }

  8.        @Override

  9.        public void handleFetchError(String key, Exception e) {

  10.            // implementation

  11.        }

  12.        @Override

  13.        public void handleError(String msg, Exception e) {

  14.            // implementation

  15.        }

  16.    };

  17. }

与RateLimitKeyGenerator 对象类似,RateLimiterErrorHandler 也会被自动配置。

8.总结

在这篇文章中,我们看到了怎样在Spring Cloud Netflix Zuul和Spring Cloud Zuul RateLimit中使用速率限制API。 一如既往,这篇文章的完整代码能在GitHub上找到。

推荐: 【SFA官方译】:使用Spring Security保护REST API

上一篇:SpringBoot全局异常与数据校验

关注公众号


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存