在前面我们知道Spring Cloud Gateway实现了一个RequestRateLimiter的过滤器,该过滤器会对访问到当前网关的所有请求执行限流过滤器、如果被限流,默认情况下回响应Http-429-Too Many Requests。RequestRateLimiterGatewayFilterFactory默认提供了RedisRateLimiter的限流实现,它采用令牌桶的算法实现限流功能。如下我们可以在Spring Cloud Gateway配置中配置过滤器—RequestRateLimiter:
spring:
cloud:
gateway:
routes:
- id: redis_limiter
uri: https://example.org
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
server:
port: 8081
redis-rate-Limiter有两个属性,分别为replenishRate表示令牌桶中令牌得到填充速度,代表允许每秒执行的请求数,burstCapacity表示令牌桶的容量,表示每秒用户最大执行的请求数。redis的限流是基于Stripe实现,需要加入下面的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
我们可以实现KeyResolver接口设置限流请求的key,指定对当前请求中的哪些因素进行限流,如下代码我们队请求的IP进行限流:
public class IpAddressKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
KeyResolver的默认实现为PrincipalNameKeyResolver,它会从ServerWebExchange 检索Principal并且调用Principal.getName方法。默认情况下如果KeyResolver没有获取到key,请求将会被拒绝,我们可以根据限流过滤器的参数调整分别为denyEmptyKey和emptyKeyStatus。如下为IP限流的配置:
spring:
cloud:
gateway:
routes:
- id: redis_limiter
uri: https://example.org
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
denyEmptyKey: true
emptyKeyStatus: SERVICE_UNAVAILABLE
keyResolver: '#{@ipAddressKeyResolver}'
server:
port: 8081
我们调用接口时,redis会生成两个key分别为request_rater_limiter.{ip}.timestamp和request_rater_limiter.{ip}.tokens。Spring Cloud Gateway只实现了基于Redis的RateLimiter限流模式,我们可以通过实现AbstractRateLimiter自定义xianliuq,通过配置指定限流器:
rateLimiter: #{@rateLimiterName}
Spring Cloud Alibaba Sentinel 从1.6版本提供了对Spring Cloud Gateway的支持,支持两种资源维度的限流,分别为Route维度和自定义API维度,可以利用提供的API来自定义API分组,然后针对这些组限流。我们需要引入Sentinel-Adapter依赖包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.7.1</version>
</dependency>
在前面我们介绍Sentinel时,我们使用FlowRule提供限流规则,而在Spring Cloud Gateway 中Sentinel使用GatewayFlowRule作为限流规则。如下为一个简单的例子:
private void initGatewayRules() {
Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>();
GatewayFlowRule rule = new GatewayFlowRule().setCount(5).setIntervalSec(1).setResource("gateway-sentinel-limiter");
gatewayFlowRules.add(rule);
GatewayRuleManager.loadRules(gatewayFlowRules);
}
与FlowRule类似,GatewayFlowRule提供了以下属性:这里我们直接复制源码中的设置属性的部分代码并且注释,代码如下所示:
//设置资源名称,可以是网关配置中的route名称或者用户自定义的API分组
public GatewayFlowRule setResource(java.lang.String resource) { }
//资源模型,限流针对API Gateway的route还是用户在sentinel定义的API分组
public GatewayFlowRule setResourceMode(int resourceMode) { }
// 限流指标维度,同FlowRule的grade字段
public GatewayFlowRule setGrade(int grade) { }
//同FlowRule的controlBehavior字段,支持快速失败和匀速排队
public GatewayFlowRule setControlBehavior(int controlBehavior) { }
//限流阈值
public GatewayFlowRule setCount(double count) { }
//统计时间窗口,单位为秒,默认是1秒
public GatewayFlowRule setIntervalSec(long intervalSec) { }
//应对突发请求时额外允许的请求数目
public GatewayFlowRule setBurst(int burst) { }
//匀速排队下最长排队时间
public GatewayFlowRule setMaxQueueingTimeoutMs(int maxQueueingTimeoutMs) { }
//参数限流配置,若不提供,则代表不针对参数进行限流。从请求中提取参数的策略,下面的类为GatewayParamFlowItem提供的属性
public GatewayFlowRule setParamItem(GatewayParamFlowItem paramItem) { }
public class GatewayParamFlowItem {
private java.lang.Integer index;
//解析策略:目前支持IP,HOST,任意Header和任意URL参数四种模式,
private int parseStrategy;
//为Header和URL策略时的Header 名称或者URL参数名称
private java.lang.String fieldName;
//下面的两个目前没有实现,后续可能会实现
private java.lang.String pattern;
private int matchStrategy;
}
Sentinel对Spring Cloud Gateway限流的支持其实是一个全局的过滤器,我们可以将全局过滤器注入到容器中,然后全局过滤器就会生效,如下为Sentinel在Spring Cloud Gateway中使用的配置:
Configuration
public class SentinelRateLimiterConfiguation {
private final List<ViewResolver> viewResolverList;
private final ServerCodecConfigurer serverCodecConfigurer;
//注入视图解析器
public SentinelRateLimiterConfiguation(ObjectProvider<List<ViewResolver>> viewResolverList, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolverList = viewResolverList.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//注入Sentinel全局过滤器SentinelGatewayFilter
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//注入限流异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolverList, serverCodecConfigurer);
}
//初始化限流规则
@PostConstruct
public void initRules() {
initGatewayRules();
}
private void initGatewayRules() {
Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>();
GatewayFlowRule rule = new GatewayFlowRule().setCount(5).setIntervalSec(1).setResource("gateway-sentinel-limiter");
gatewayFlowRules.add(rule);
GatewayRuleManager.loadRules(gatewayFlowRules);
}
}
配置完Sentinel之后,我们需要配置Spring Cloud Gateway网关的相关配置,配置如下所示:
spring:
cloud:
gateway:
routes:
- id: gateway-sentinel-limiter
uri: https://example.org
predicates:
-path=/sentinel/rate
前面我们说过,除了使用route限流之外,我们还可以自定义分组进行限流,实际上就是让多个route使用一个限流规则。加入我们需要以下两个route使用一个限流规则:
spring:
cloud:
gateway:
routes:
- id: exmaple-sentinel-limiter
uri: https://example.org
predicates:
-path=/sentinel/rate
- id: example1-sentinel-limiter
uri: https://example1.org
predicates:
-path=/sentinel1/rate
下面我们可以将上面的两个路由配置到同一个分组,然后使用同一个限流规则,如下代码将上面的两个route定义到一个分组中:
private void initCustomizedApis() {
ApiDefinition api = new ApiDefinition("customized-apis");
Set<ApiPredicateItem> apiPredicateItems = new HashSet<ApiPredicateItem>();
apiPredicateItems.add(new ApiPathPredicateItem().setPattern("/sentinel/rate"));
apiPredicateItems.add(new ApiPathPredicateItem().setPattern("/sentinel1/rate").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX));
api.setPredicateItems(apiPredicateItems);
}
定义完分组之后,我们在定义限流规则时设置资源时即setResource()时,resource设置为customized-apis即可。代码如下:
private void initGatewayRules() {
Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>();
GatewayFlowRule rule = new GatewayFlowRule().setCount(5).setIntervalSec(1).setResource("customized-apis").setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
gatewayFlowRules.add(rule);
GatewayRuleManager.loadRules(gatewayFlowRules);
}