spring cloud netflix zuul是一個包含netflix zuul的 開源網(wǎng)關。它為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> |
首先,讓我們創(chuàng)建幾個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下,我們?yōu)槎它c提供速率限制配置。該限屬性指定的時間端點可以在內(nèi)部被稱為數(shù)字刷新間隔。
我們可以看到,我們?yōu)閟ervicesimple 端點添加了每60秒5個請求的速率限制。相比之下, serviceadvanced的速率限制為每2秒1個請求。
該類型配置指定其速率限制的方法,以下是可能的值:
- origin - 基于用戶原始請求的速率限制
- url - 基于下游服務的請求路徑的速率限制
- user - 基于經(jīng)過身份驗證的用戶名或“匿名”的速率限制
- no value - 充當每項服務的全局配置。要使用這種方法,請不要設置參數(shù)'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進行一次調(diào)用。請求成功,因為它在速率限制內(nèi)。
另一個關鍵點是,對于每個響應,我們返回標頭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]: 調(diào)用端點的剩余嘗試次數(shù)
- x-ratelimit-reset- [key]:為端點配置 的刷新間隔的剩余毫秒數(shù)
另外,如果我們再次立即觸發(fā)相同的端點,我們可以得到:
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 |
請注意減少的剩余嘗試次數(shù)和剩余的毫秒數(shù)。
讓我們看看當我們超過速率限制時會發(fā)生什么:
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()); } |
在這里,我們快速連續(xù)兩次調(diào)用,由于我們已將速率限制配置為每2秒一個請求,因此第二個調(diào)用將失敗。結果,錯誤代碼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秒鐘。這是為端點配置的刷新間隔。最后,我們再次觸發(fā)端點并獲得成功的響應。
自定義密鑰生成器
我們可以使用自定義密鑰生成器自定義響應頭中發(fā)送的密鑰。這很有用,因為應用程序可能需要控制除type屬性提供的選項之外的密鑰策略。
例如,這可以通過創(chuàng)建自定義的ratelimitkeygenerator實現(xiàn)類來完成。我們可以添加更多的限定符或完全不同的東西:
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自動配置。
自定義錯誤處理
該框架支持速率限制數(shù)據(jù)存儲的各種實現(xiàn)。例如,提供了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上 找到本文的完整代碼
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://www.jdon.com/50654