網(wǎng)關(guān)限流三步走
這里采用令牌桶計(jì)數(shù)的方式做限流,總共分三步
準(zhǔn)備工作
我們的Best Practice是基于Redis來實(shí)現(xiàn)限流,因此要保證本地啟動了Redis服務(wù)让虐。同時(shí)將下列配置加入到Gateway的配置文件中:
spring:
application:
name: gateway-service
redis:
host: localhost
port: 6379
database: 0
這里是配置Redis連接信息的孽文,假如你不配置的話,Gateway也會嘗試用默認(rèn)配置項(xiàng)來連接Redis真友。但如果你在Redis配置信息中提供了錯誤的IP或者Port的話,調(diào)用方法時(shí)依然會成功紧帕,不過限流功能就失效了锻狗,因?yàn)榈讓拥腘etty服務(wù)無法連接到Redis,也就無法提供限流支持。但Gateway為了保證服務(wù)可用性轻纪,限流功能的異常并不會阻礙正常的方法調(diào)用油额。
Key Resolver
Gateway的限流組件要求定義一個(gè)Key Resolver用來對每次路由請求生成一個(gè)Key,這個(gè)Key就是一個(gè)限流分組的標(biāo)識刻帚,每個(gè)Key相當(dāng)于一個(gè)令牌桶潦嘶。假如我們限定了一個(gè)服務(wù)每秒只能被調(diào)用3次,這個(gè)限制會對不同的Key單獨(dú)計(jì)數(shù)崇众,我們把調(diào)用方機(jī)器的Host Name作為限流Key掂僵,那么來自同一臺機(jī)器的調(diào)用將落到同一個(gè)Key下面,也就是說在這個(gè)場景下顷歌,每臺機(jī)器都獨(dú)立計(jì)算單位時(shí)間調(diào)用量锰蓬。
創(chuàng)建Key Resolver的方式很簡單:
@Bean
public KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getHostName());
}
上面的例子創(chuàng)建了基于Host Name的令牌生成器,我們可以根據(jù)自己的業(yè)務(wù)來選擇合適的Key眯漩,比如說可以在接口層面做限流(使用接口的Path作為Key)芹扭,還可以從Request中提取業(yè)務(wù)字段作為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
在上面的限流配置中赦抖,我們主要關(guān)注最后3行中的屬性:
? key-resolver:這里注入的就是在上一步中我們定義的Key Resolver舱卡,它使用SpEL表達(dá)式從Spring上下文中獲取指定Bean
? replenishRate:令牌桶每秒的平均填充速度
? burstCapacity:令牌桶總量
java中如何配置網(wǎng)關(guān)限流
@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();
}
}
小細(xì)節(jié)
假如我們不想依賴Redis的話,還有其他選擇方案嗎队萤?必須有轮锥,但是要靠大家自己動動手。如果大家想借助其他存儲介質(zhì)實(shí)現(xiàn)限流要尔,可以參考RedisRateLimiter這個(gè)類的實(shí)現(xiàn)舍杜,通過繼承AbstractRateLimiter來創(chuàng)建一個(gè)自定義的計(jì)數(shù)器。