网关限流三步走
这里采用令牌桶计数的方式做限流,总共分三步
准备工作
我们的Best Practice是基于Redis来实现限流,因此要保证本地启动了Redis服务。同时将下列配置加入到Gateway的配置文件中:
spring:
application:
name: gateway-service
redis:
host: localhost
port: 6379
database: 0
这里是配置Redis连接信息的,假如你不配置的话,Gateway也会尝试用默认配置项来连接Redis。但如果你在Redis配置信息中提供了错误的IP或者Port的话,调用方法时依然会成功,不过限流功能就失效了,因为底层的Netty服务无法连接到Redis,也就无法提供限流支持。但Gateway为了保证服务可用性,限流功能的异常并不会阻碍正常的方法调用。
Key Resolver
Gateway的限流组件要求定义一个Key Resolver用来对每次路由请求生成一个Key,这个Key就是一个限流分组的标识,每个Key相当于一个令牌桶。假如我们限定了一个服务每秒只能被调用3次,这个限制会对不同的Key单独计数,我们把调用方机器的Host Name作为限流Key,那么来自同一台机器的调用将落到同一个Key下面,也就是说在这个场景下,每台机器都独立计算单位时间调用量。
创建Key Resolver的方式很简单:
@Bean
public KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getHostName());
}
上面的例子创建了基于Host Name的令牌生成器,我们可以根据自己的业务来选择合适的Key,比如说可以在接口层面做限流(使用接口的Path作为Key),还可以从Request中提取业务字段作为Key(比如用户ID等)。
配置过滤器
spring:
cloud:
gateway:
routes:
- id: feignapi
uri: lb://FEIGN-SERVICE-PROVIDER
predicates:
- Path=/feign-api/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
key-resolver: '#{@remoteAddrKeyResolver}'
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
在上面的限流配置中,我们主要关注最后3行中的属性:
key-resolver:这里注入的就是在上一步中我们定义的Key Resolver,它使用SpEL表达式从Spring上下文中获取指定Bean
replenishRate:令牌桶每秒的平均填充速度
burstCapacity:令牌桶总量
java中如何配置网关限流
@Configuration
public class RedisLimiterConfiguration {
@Bean
@Primary
public KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
@Bean
@Primary
public RedisRateLimiter redisRateLimiterUser() {
return new RedisRateLimiter(1,2);
}
}
@Configuration
public class GatewayConfiguration {
@Autowired
private TimerFilter timerFilter;
@Autowired
private KeyResolver remoteAddrKeyResolver;
@Autowired
private RedisRateLimiter redisRateLimiter;
@Bean
@Order
public RouteLocator customizedRoutes(RouteLocatorBuilder routeLocatorBuilder){
return routeLocatorBuilder.routes()
.route(r->r.path("/java/**")
.and().method(HttpMethod.GET)
.filters(f->f.stripPrefix(1)
.requestRateLimiter(c->{
c.setKeyResolver(remoteAddrKeyResolver);
c.setRateLimiter(redisRateLimiter);
c.setStatusCode(HttpStatus.BAD_GATEWAY);
})
)
.uri("lb://feign-client")
)
.route(r->r.path("/seckill/**")
.and().after(ZonedDateTime.now().plusMinutes(1))
.filters(f->f.stripPrefix(1))
.uri("lb://feign-client")
)
.build();
}
}
小细节
假如我们不想依赖Redis的话,还有其他选择方案吗?必须有,但是要靠大家自己动动手。如果大家想借助其他存储介质实现限流,可以参考RedisRateLimiter这个类的实现,通过继承AbstractRateLimiter来创建一个自定义的计数器。