spring cloud netflix zuul是一个包含netflix zuul的 开源网关。它为spring boot应用程序添加了一些特定功能。不幸的是,开箱即用不提供速率限制。
除了spring cloud netflix zuul依赖项之外,我们还需要将spring cloud zuul ratelimit 添加到我们的应用程序的pom.xml中:
1
2
3
4
5
6
7
8
9
|
<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类,有两个端点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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属性 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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下,我们为端点提供速率限制配置。该限属性指定的时间端点可以在内部被称为数字刷新间隔。
我们可以看到,我们为servicesimple 端点添加了每60秒5个请求的速率限制。相比之下, serviceadvanced的速率限制为每2秒1个请求。
该类型配置指定其速率限制的方法,以下是可能的值:
- origin - 基于用户原始请求的速率限制
- url - 基于下游服务的请求路径的速率限制
- user - 基于经过身份验证的用户名或“匿名”的速率限制
- no value - 充当每项服务的全局配置。要使用这种方法,请不要设置参数'type'
接下来,让我们测试一下速率限制:
1
2
3
4
5
6
7
8
9
10
11
12
|
@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进行一次调用。请求成功,因为它在速率限制内。
另一个关键点是,对于每个响应,我们返回标头header,为我们提供有关速率限制的更多信息。对于上述请求,我们将获得以下标头:
1
2
3
4
5
|
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]:为端点配置 的刷新间隔的剩余毫秒数
另外,如果我们再次立即触发相同的端点,我们可以得到:
1
2
3
4
5
|
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
|
请注意减少的剩余尝试次数和剩余的毫秒数。
让我们看看当我们超过速率限制时会发生什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@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());
}
|
在这里,我们快速连续两次调用,由于我们已将速率限制配置为每2秒一个请求,因此第二个调用将失败。结果,错误代码429(too many requests)返回给客户端。以下是达到速率限制时返回的标头:
1
2
3
4
5
|
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实现类来完成。我们可以添加更多的限定符或完全不同的东西:
1
2
3
4
5
6
7
8
9
10
11
|
@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方法名称附加到键。例如:
1
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@bean
public ratelimitererrorhandler ratelimiterrorhandler() {
return new defaultratelimitererrorhandler() {
@override
public void handlesaveerror(string key, exception e) {
<i> // implementation</i>
}
@override
public void handlefetcherror(string key, exception e) {
<i> // implementation</i>
}
@override
public void handleerror(string msg, exception e) {
<i> // implementation</i>
}
};
}
|
与ratelimitkeygenerator bean 类似 ,也将自动配置ratelimitererrorhandler bean。
在github上 找到本文的完整代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://www.jdon.com/50654