Spring Cloud Gateway 原生支持接口限流该怎么玩
关于Spring Cloud Gateway
SpringCloudGateway是spring官方基于Spring5.0,Spring Boot2.0 和Project Reactor等技术开发的网关,Spring云网关旨在提供一种简单高效的路由API的方法。Spring cloud 生态系的网关。目标是替代Netflix ZUUL, 其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关的基本功能,例如: 安全,监控/埋点,和限流等
Zuul:构建高可用网关之多维度限流
开始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>
按照请求的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表达式去的对应的bean
- StripPrefix=1
所有关于admin 的请求都会转发过来 , - StripPrefix=1 去掉admin 如
http://localhost:8080/admin/api/user => http://pigx-upms:8081/api/user
配置bean
在启动类配置
/**
*自定义限流标志的key,多个维度可以从这里入手
*exchange对象中获取服务ID,请求信息,用户信息等
*/
@Bean
KeyResolver remoteAddrkeyResolver(){
return exchange -> Mono.just(
exchange.getRequest.getRemoteAddress().getHostName());
}
ok完成
压力测试
开发5个线程
Redis数据变化
我们使用redis的monitor命令,实时查看redis的操作情况
会发现在redis中会操作2个key
request_rate_limiter.{xxx}.timestamp
request_rate_limiter.{xxx}.tokens
实现原理
625766066-5b38c5499d478_articlex.pngspring cloud Gateway 默认实现Redis限流,如果扩展只需要实现ratelimter接口既可
RedisRateLimter 的核心代码,判断是否取到令牌的实现,通过调用 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");
// 这里是核心,执行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)));
}
1496499253-5b38c549a9760_articlex.png
网友评论