Spring Cloud Netflix Zuul是一个包含 Netflix Zuul的 开源网关。它为Spring Boot应用程序添加了一些特定功能。不幸的是,开箱即用不提供速率限制。
除了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>
首先,让我们创建几个REST端点,我们将在其上应用速率限制。
下面是一个简单的Spring Controller类,有两个端点:
@Controller @RequestMapping(<font>"/greeting"</font><font>) <b>public</b> <b>class</b> GreetingController { @GetMapping(</font><font>"/simple"</font><font>) <b>public</b> ResponseEntity<String> getSimple() { <b>return</b> ResponseEntity.ok(</font><font>"Hi!"</font><font>); } @GetMapping(</font><font>"/advanced"</font><font>) <b>public</b> ResponseEntity<String> getAdvanced() { <b>return</b> ResponseEntity.ok(</font><font>"Hello, how you doing?"</font><font>); } } </font>
让我们在application.yml文件中添加以下Zuul属性 :
zuul: routes: serviceSimple: path: /greeting/simple url: forward:/ serviceAdvanced: path: /greeting/advanced url: forward:/ ratelimit: enabled: <b>true</b> repository: JPA policy-list: serviceSimple: - limit: 5 refresh-interval: 60 type: - origin serviceAdvanced: - limit: 1 refresh-interval: 2 type: - origin strip-prefix: <b>true</b>
在zuul.routes下,我们提供端点详细信息。在zuul.ratelimit.policy-list下,我们为端点提供速率限制配置。该限属性指定的时间端点可以在内部被称为数字刷新间隔。
我们可以看到,我们为serviceSimple 端点添加了每60秒5个请求的速率限制。相比之下, serviceAdvanced的速率限制为每2秒1个请求。
该类型配置指定其速率限制的方法,以下是可能的值:
接下来,让我们测试一下速率限制:
@Test <b>public</b> <b>void</b> whenRequestNotExceedingCapacity_thenReturnOkResponse() { ResponseEntity<String> response = restTemplate.getForEntity(SIMPLE_GREETING, String.<b>class</b>); assertEquals(OK, response.getStatusCode()); HttpHeaders headers = response.getHeaders(); String key = <font>"rate-limit-application_serviceSimple_127.0.0.1"</font><font>; assertEquals(</font><font>"5"</font><font>, headers.getFirst(HEADER_LIMIT + key)); assertEquals(</font><font>"4"</font><font>, headers.getFirst(HEADER_REMAINING + key)); assertEquals(</font><font>"60000"</font><font>, headers.getFirst(HEADER_RESET + key)); } </font>
在这里,我们只对一个端点/ greeting / simple进行一次调用。请求成功,因为它在速率限制内。
另一个关键点是,对于每个响应,我们返回标头Header,为我们提供有关速率限制的更多信息。对于上述请求,我们将获得以下标头:
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-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
请注意减少的剩余尝试次数和剩余的毫秒数。
让我们看看当我们超过速率限制时会发生什么:
@Test <b>public</b> <b>void</b> whenRequestExceedingCapacity_thenReturnTooManyRequestsResponse() throws InterruptedException { ResponseEntity<String> response = <b>this</b>.restTemplate.getForEntity(ADVANCED_GREETING, String.<b>class</b>); assertEquals(OK, response.getStatusCode()); <b>for</b> (<b>int</b> i = 0; i < 2; i++) { response = <b>this</b>.restTemplate.getForEntity(ADVANCED_GREETING, String.<b>class</b>); } assertEquals(TOO_MANY_REQUESTS, response.getStatusCode()); HttpHeaders headers = response.getHeaders(); String key = <font>"rate-limit-application_serviceAdvanced_127.0.0.1"</font><font>; assertEquals(</font><font>"1"</font><font>, headers.getFirst(HEADER_LIMIT + key)); assertEquals(</font><font>"0"</font><font>, headers.getFirst(HEADER_REMAINING + key)); assertNotEquals(</font><font>"2000"</font><font>, headers.getFirst(HEADER_RESET + key)); TimeUnit.SECONDS.sleep(2); response = <b>this</b>.restTemplate.getForEntity(ADVANCED_GREETING, String.<b>class</b>); assertEquals(OK, response.getStatusCode()); } </font>
在这里,我们快速连续两次调用,由于我们已将速率限制配置为每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秒钟。这是为端点配置的刷新间隔。最后,我们再次触发端点并获得成功的响应。
自定义密钥生成器
我们可以使用自定义密钥生成器自定义响应头中发送的密钥。这很有用,因为应用程序可能需要控制除type属性提供的选项之外的密钥策略。
例如,这可以通过创建自定义的RateLimitKeyGenerator实现类来完成。我们可以添加更多的限定符或完全不同的东西:
@Bean <b>public</b> RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) { <b>return</b> <b>new</b> DefaultRateLimitKeyGenerator(properties, rateLimitUtils) { @Override <b>public</b> String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) { <b>return</b> <b>super</b>.key(request, route, policy) + <font>"_"</font><font> + request.getMethod(); } }; } </font>
上面的代码将REST方法名称附加到键。例如:
X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5
另一个关键点是 RateLimitKeyGenerator bean将由spring-cloud-zuul-ratelimit自动配置。
自定义错误处理
该框架支持速率限制数据存储的各种实现。例如,提供了Spring Data JPA和Redis。默认情况下,使用DefaultRateLimiterErrorHandler 类将故障记录为错误。
当我们需要以不同方式处理错误时,我们可以定义一个自定义的RateLimiterErrorHandler bean:
@Bean <b>public</b> RateLimiterErrorHandler rateLimitErrorHandler() { <b>return</b> <b>new</b> DefaultRateLimiterErrorHandler() { @Override <b>public</b> <b>void</b> handleSaveError(String key, Exception e) { <font><i>// implementation</i></font><font> } @Override <b>public</b> <b>void</b> handleFetchError(String key, Exception e) { </font><font><i>// implementation</i></font><font> } @Override <b>public</b> <b>void</b> handleError(String msg, Exception e) { </font><font><i>// implementation</i></font><font> } }; } </font>
与RateLimitKeyGenerator bean 类似 ,也将自动配置RateLimiterErrorHandler bean。
在GitHub上 找到本文的完整代码