關(guān)于pig:
基于Spring Cloud弛秋、oAuth2.0開發(fā)基于Vue前后分離的開發(fā)平臺(tái)踪区,支持賬號(hào)、短信赤炒、SSO等多種登錄氯析,提供配套視頻開發(fā)教程。
碼云地址:https://gitee.com/log4j/pig
關(guān)于 Spring Cloud Gateway
SpringCloudGateway是Spring官方基于Spring 5.0莺褒,Spring Boot 2.0和Project Reactor等技術(shù)開發(fā)的網(wǎng)關(guān)掩缓,Spring云網(wǎng)關(guān)旨在提供一種簡(jiǎn)單而有效的路由API的方法。Spring Cloud Gateway作為Spring Cloud生態(tài)系中的網(wǎng)關(guān)遵岩,目標(biāo)是替代Netflix ZUUL你辣,其不僅提供統(tǒng)一的路由方式,并且基于Filter鏈的方式提供了網(wǎng)關(guān)基本的功能旷余,例如:安全绢记,監(jiān)控/埋點(diǎn),和限流等正卧。
zuul如何實(shí)現(xiàn)多維度限流請(qǐng)參考我的博客
Zuul:構(gòu)建高可用網(wǎng)關(guān)之多維度限流
開始Gateway 限流
POM 依賴
<!--spring cloud gateway依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--基于 reactive stream 的redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置按照請(qǐng)求IP 的限流
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: lb://pigx-upms
order: 10000
predicates:
- Path=/admin/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶的容積
redis-rate-limiter.burstCapacity: 3 # 流速 每秒
key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表達(dá)式去的對(duì)應(yīng)的bean
- StripPrefix=1
配置bean蠢熄,多維度限流量的入口
/**
* 自定義限流標(biāo)志的key,多個(gè)維度可以從這里入手
* exchange對(duì)象中獲取服務(wù)ID炉旷、請(qǐng)求信息签孔,用戶信息等
*/
@Bean
KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
OK 完成叉讥。
壓力測(cè)試
并發(fā)5個(gè)線程。
Redis 數(shù)據(jù)變化
我們使用redis的monitor 命令饥追,實(shí)時(shí)查看redis 的操作情況图仓。
會(huì)發(fā)現(xiàn)在redis中會(huì)操作兩個(gè)key
- request_rate_limiter.{xxx}.timestamp
-
request_rate_limiter.{xxx}.tokens
實(shí)現(xiàn)原理
Spring Cloud Gateway 默認(rèn)實(shí)現(xiàn) Redis限流,如果擴(kuò)展只需要實(shí)現(xiàn)ratelimter接口即可但绕。
RedisRateLimter 的核心代碼救崔,判斷是否取到令牌的實(shí)現(xiàn),通過調(diào)用 redis的LUA 腳本捏顺。
public Mono<Response> isAllowed(String routeId, String id) {
Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);
int replenishRate = routeConfig.getReplenishRate();
int burstCapacity = routeConfig.getBurstCapacity();
try {
List<String> keys = getKeys(id);
returns unixtime in seconds.
List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
Instant.now().getEpochSecond() + "", "1");
// 這里是核心六孵,執(zhí)行redis 的LUA 腳本。
Flux<List<Long>> flux =
this.redisTemplate.execute(this.script, keys, scriptArgs);
return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
.reduce(new ArrayList<Long>(), (longs, l) -> {
longs.addAll(l);
return longs;
}) .map(results -> {
boolean allowed = results.get(0) == 1L;
Long tokensLeft = results.get(1);
Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
if (log.isDebugEnabled()) {
log.debug("response: " + response);
}
return response;
});
}
catch (Exception e) {
log.error("Error determining if user allowed from redis", e);
}
return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
}