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.Controller例子

首先,我们在将要应用速率限制的地方创建一些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?");

        }

    }

 

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

4.Zuul配置

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

    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-基于认证用户名或匿名用户的速率限制

  • 空值-每一个服务使用一个全局配置,要使用这个方式只需要不设置参数‘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]: 端点的限制配置 X-RateLimit-Remaining-[key]: 调用端点的剩余尝试次数*X-RateLimit-Reset-[key]: 端点配置刷新间隔的剩余毫秒次数

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

    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秒只能1次请求的速率限制,第二次调用将会失败。错误码429(请求频繁)将作为结果返回给客户端。

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

    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秒,这是为端点刷新区间配置。最后,我们再次调用端点并能获得成功的响应。

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对象将被spring-cloud-zuul-ratelimit自动配置.

7.个性化错误处理

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

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

    @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 对象类似,RateLimiterErrorHandler 也会被自动配置。

8.总结

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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐