詳解Spring Cloud Netflix Zuul中的速率限制
Spring Cloud Netflix Zuul是一個(gè)包含Netflix Zuul的 開(kāi)源網(wǎng)關(guān)。它為Spring Boot應(yīng)用程序添加了一些特定功能。不幸的是,開(kāi)箱即用不提供速率限制。
除了Spring Cloud Netflix Zuul依賴(lài)項(xiàng)之外,我們還需要將Spring Cloud Zuul RateLimit 添加到我們的應(yīng)用程序的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>
首先,讓我們創(chuàng)建幾個(gè)REST端點(diǎn),我們將在其上應(yīng)用速率限制。
下面是一個(gè)簡(jiǎn)單的Spring Controller類(lèi),有兩個(gè)端點(diǎn):
@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?");
}
}
讓我們?cè)赼pplication.yml文件中添加以下Zuul屬性 :
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下,我們提供端點(diǎn)詳細(xì)信息。在zuul.ratelimit.policy-list下,我們?yōu)槎它c(diǎn)提供速率限制配置。該限屬性指定的時(shí)間端點(diǎn)可以在內(nèi)部被稱(chēng)為數(shù)字刷新間隔。
我們可以看到,我們?yōu)閟erviceSimple 端點(diǎn)添加了每60秒5個(gè)請(qǐng)求的速率限制。相比之下, serviceAdvanced的速率限制為每2秒1個(gè)請(qǐng)求。
該類(lèi)型配置指定其速率限制的方法,以下是可能的值:
- origin - 基于用戶(hù)原始請(qǐng)求的速率限制
- url - 基于下游服務(wù)的請(qǐng)求路徑的速率限制
- user - 基于經(jīng)過(guò)身份驗(yàn)證的用戶(hù)名或“匿名”的速率限制
- No value - 充當(dāng)每項(xiàng)服務(wù)的全局配置。要使用這種方法,請(qǐng)不要設(shè)置參數(shù)'type'
接下來(lái),讓我們測(cè)試一下速率限制:
@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));
}
在這里,我們只對(duì)一個(gè)端點(diǎn)/ greeting / simple進(jìn)行一次調(diào)用。請(qǐng)求成功,因?yàn)樗谒俾氏拗苾?nèi)。
另一個(gè)關(guān)鍵點(diǎn)是,對(duì)于每個(gè)響應(yīng),我們返回標(biāo)頭Header,為我們提供有關(guān)速率限制的更多信息。對(duì)于上述請(qǐng)求,我們將獲得以下標(biāo)頭:
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
解釋?zhuān)?/p>
- X-RateLimit-Limit- [key]:為端點(diǎn)配置 的限制
- X-RateLimit-Remaining- [key]: 調(diào)用端點(diǎn)的剩余嘗試次數(shù)
- X-RateLimit-Reset- [key]:為端點(diǎn)配置 的刷新間隔的剩余毫秒數(shù)
另外,如果我們?cè)俅瘟⒓从|發(fā)相同的端點(diǎn),我們可以得到:
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
請(qǐng)注意減少的剩余嘗試次數(shù)和剩余的毫秒數(shù)。
讓我們看看當(dāng)我們超過(guò)速率限制時(shí)會(huì)發(fā)生什么:
@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秒一個(gè)請(qǐng)求,因此第二個(gè)調(diào)用將失敗。結(jié)果,錯(cuò)誤代碼429(Too Many Requests)返回給客戶(hù)端。以下是達(dá)到速率限制時(shí)返回的標(biāo)頭:
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秒鐘。這是為端點(diǎn)配置的刷新間隔。最后,我們?cè)俅斡|發(fā)端點(diǎn)并獲得成功的響應(yīng)。
自定義密鑰生成器
我們可以使用自定義密鑰生成器自定義響應(yīng)頭中發(fā)送的密鑰。這很有用,因?yàn)閼?yīng)用程序可能需要控制除type屬性提供的選項(xiàng)之外的密鑰策略。
例如,這可以通過(guò)創(chuàng)建自定義的RateLimitKeyGenerator實(shí)現(xiàn)類(lèi)來(lái)完成。我們可以添加更多的限定符或完全不同的東西:
@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方法名稱(chēng)附加到鍵。例如:
X-RateLimit-Limit-rate-limit-application_serviceSimple_127.0.0.1_GET: 5
另一個(gè)關(guān)鍵點(diǎn)是 RateLimitKeyGenerator bean將由spring-cloud-zuul-ratelimit自動(dòng)配置。
自定義錯(cuò)誤處理
該框架支持速率限制數(shù)據(jù)存儲(chǔ)的各種實(shí)現(xiàn)。例如,提供了Spring Data JPA和Redis。默認(rèn)情況下,使用DefaultRateLimiterErrorHandler 類(lèi)將故障記錄為錯(cuò)誤。
當(dāng)我們需要以不同方式處理錯(cuò)誤時(shí),我們可以定義一個(gè)自定義的RateLimiterErrorHandler bean:
@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 類(lèi)似 ,也將自動(dòng)配置RateLimiterErrorHandler bean。
在GitHub上 找到本文的完整代碼
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 詳解SpringCloud Zuul過(guò)濾器返回值攔截
- 深入理解Spring Cloud Zuul過(guò)濾器
- 深入解析Spring Cloud內(nèi)置的Zuul過(guò)濾器
- SpringCloud Zuul實(shí)現(xiàn)動(dòng)態(tài)路由
- SpringCloud Zuul在何種情況下使用Hystrix及問(wèn)題小結(jié)
- 淺談Spring Cloud zuul http請(qǐng)求轉(zhuǎn)發(fā)原理
- SpringCloud實(shí)戰(zhàn)之Zuul網(wǎng)關(guān)服務(wù)
- Spring Cloud Zuul添加過(guò)濾器過(guò)程解析
相關(guān)文章
Spring security實(shí)現(xiàn)記住我下次自動(dòng)登錄功能過(guò)程詳解
這篇文章主要介紹了Spring security實(shí)現(xiàn)記住我下次自動(dòng)登錄功能過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Java中的鎖與鎖的狀態(tài)升級(jí)詳細(xì)解讀
這篇文章主要介紹了Java中的鎖與鎖的狀態(tài)升級(jí)詳細(xì)解讀,Java 1.6以后官方針對(duì)鎖的優(yōu)化,主要是增加了兩種新的鎖:偏向鎖和輕量級(jí)鎖,再加上本身重量級(jí)鎖,那么鎖基本上可以大致分為這三種,它們之間的區(qū)別主要是體現(xiàn)在等待時(shí)間上面,需要的朋友可以參考下2024-01-01
springboot+mybaties項(xiàng)目中掃描不到@mapper注解的解決方法
本文主要介紹了springboot+mybaties項(xiàng)目中掃描不到@mapper注解的解決方法,該報(bào)錯(cuò)表明掃描不到Mapper層,具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05
深入探討Druid動(dòng)態(tài)數(shù)據(jù)源的實(shí)現(xiàn)方式
Druid是一個(gè)高性能的實(shí)時(shí)分析數(shù)據(jù)庫(kù),它可以處理大規(guī)模數(shù)據(jù)集的快速查詢(xún)和聚合操作,在Druid中,動(dòng)態(tài)數(shù)據(jù)源是一種可以在運(yùn)行時(shí)動(dòng)態(tài)添加和刪除的數(shù)據(jù)源,使用動(dòng)態(tài)數(shù)據(jù)源,您可以在Druid中輕松地處理不斷變化的數(shù)據(jù)集,本文講給大家介紹一下Druid動(dòng)態(tài)數(shù)據(jù)源該如何實(shí)現(xiàn)2023-08-08
springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié)
這篇文章主要介紹了springboot整合mybatis中的問(wèn)題及出現(xiàn)的一些問(wèn)題小結(jié),本文給出了解決方案,需要的朋友可以參考下2018-11-11
IntelliJ IDEA下載GitHub私有倉(cāng)庫(kù)到本地的方法(新版)
這篇文章主要介紹了IntelliJ IDEA下載GitHub私有倉(cāng)庫(kù)到本地(新版),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Mybatis動(dòng)態(tài)Sql標(biāo)簽使用小結(jié)
本文主要介紹了Mybatis動(dòng)態(tài)Sql標(biāo)簽使用,常用的動(dòng)態(tài)sql標(biāo)簽包括?if、choose(when、otherwise)、trim(where、set)、foreach,下面就來(lái)介紹一下2024-04-04
ActiveMQ消息隊(duì)列技術(shù)融合Spring過(guò)程解析
這篇文章主要介紹了ActiveMQ消息隊(duì)列技術(shù)融合Spring過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11

