至此微服务网关系列文章已出:
- 【云原生&微服务>SCG网关篇一】为什么要有网关、生产环境如何选择网关
- 云原生&微服务>SCG网关篇二】生产上那些灰度发布方式
- 【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例
- 云原生&微服务>SCG网关篇四】Spring Cloud Gateway内置的11种PredicateFactory如何使用
- 【云原生&微服务>SCG网关篇五】Spring Cloud Gateway自定义PredicateFactory
- 【云原生&微服务>SCG网关篇六】Spring Cloud Gateway内置的18种Filter使用姿势
- 【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试
- 【云原生&微服务>SCG网关篇八】Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式
- 【云原生&微服务>SCG网关篇九】Spring Cloud Gateway集成Nacos详细案例
- 【云原生&微服务>SCG网关篇十】Spring Cloud Gateway集成Actuator、Zipkin详细案例
- 【云原生&微服务>SCG网关篇十一】Spring Cloud Gateway解决跨域问题
聊了以下问题:
- 为什么要有网关?网关的作用是什么?
- 网关的分类?
- 网关的技术选型?
- 使用网关时常用的灰度发布方式有哪些?
- Spring Cloud Gateway是什么?详细使用案例?
- Spring Cloud Gateway内置的11种PredicateFactory
- 如何自定义PredicateFactory?
- Spring Cloud Gateway内置的18种常用的Filter
- Spring Cloud Gateway基于内置Filter实现限流、熔断、重试
- Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式
- Spring Cloud Gateway集成Nacos案例
- Spring Cloud Gateway集成Actuator、Zipkin案例
- Spring Cloud Gareway如何解决CORS跨域问题
我们已经聊过了Spring Cloud Gateway的一种限流方式:使用内置的Filter(RequestRateLimiterGatewayFilterFactory
)结合Redis使用令牌桶算法实现限流;这里我们再聊一下另外一种:Spring Cloud Gateway集成Sentinel实现限流。
PS:SpringCloud版本信息:
<properties>
<spring-boot.version>2.4.2spring-boot.version>
<spring-cloud.version>2020.0.1spring-cloud.version>
<spring-cloud-alibaba.version>2021.1spring-cloud-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
sentinel服务1.6.0以上的版本可支持整合到网关进行统一流控:
sentinel提供了两种资源维度的限流:
gateway整合sentinel默认不支持 URL 粒度限流;因此通过 Spring Cloud Alibaba 接入时,将spring.cloud.sentinel.filter.enabled
配置为 false,以关闭流控控制台上的 URL 资源视图。
gateway整合Sentinel的maven依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.0</version>
</dependency>
网关的限流规则GatewayFlowRule:
网关限流规则 GatewayFlowRule 的字段如下:
可以通过GatewayRuleManager.loadRules(rules) 手动加载网关规则、 或 通过 GatewayRuleManager.register2Property(property) 注册规则(推荐方式);
特别注意:当使用 Spring Cloud Alibaba Sentinel 数据源模块时,需要注意网关流控规则数据源类型是 gw-flow,若将网关流控规则数据源指定为 flow 则不生效。
application.yml配置文件:
spring:
application:
name: nacos-gateway
cloud:
nacos:
discovery:
server-addr: 106.15.139.143:8848
gateway:
discovery:
locator:
# 开启从注册中心动态创建路由的功能
enabled: true
# 是否使用service-id的小写,默认是大写
lower-case-service-id: true
routes:
- id: gateway-nacos-service-route
# 其中配置的lb://表示从注册中心获取服务,后面的gateway-nacos-provider表示目标服务在注册中心上的服务名
uri: lb://gateway-nacos-provider
predicates:
- Path=/nacos/sentinel/** #被限流
filters:
- StripPrefix=2
# 自定义Sentinel API分组限流
- id: red_route
uri: lb://gateway-nacos-provider
predicates:
- Path=/red/** #被限流
filters:
- StripPrefix=1
- id: green_route
uri: lb://gateway-nacos-provider
predicates:
- Path=/green/** #不被限流
filters:
- StripPrefix=1
下面的三个样例均依赖此application.yml配置文件。
搞一个ConfigurationClass,在类初始化的最后阶段(@PostConstruct
标注的方法)实例化一个GatewayFlowRule
,并将其添加到GatewayRuleManager中;GatewayFlowRule
中指定针对哪个Route限流、限流的规则是什么?
package com.saint.gateway.sentinel;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
/**
* 针对某个Route限流
*
* @author Saint
*/
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 注入一个全局限流过滤器SentinelGatewayFilter
* Filter的优先级最高
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 注入限流异常处理器
* Filter的优先级最高
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 初始化限流规则
*/
@PostConstruct
public void doInit() {
initGatewayRules();
}
/**
* Route维度限流规则
*/
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// 这里表示1s仅允许通过一个请求,GatewayFlowRule构造函数中的如参为路由名
GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("gateway-nacos-service-route").setCount(1).setIntervalSec(1);
rules.add(gatewayFlowRule);
GatewayRuleManager.loadRules(rules);
}
}
当前案例表示针对routeId为gateway-nacos-service-route
的路由做限流:1s仅允许一个请求通过;
当请求被限流时会返回429状态码,响应体内容为:
{"code":429,"message":"Blocked by Sentinel: ParamFlowException"}
自定义API分组限流,将/nacos/sentinel/**
和/red/**
进行统一分组,并提供name=saint_customized_api,然后在初始化网关限流规则时,针对该name设置限流规则;
package com.saint.gateway.sentinel;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
/**
* 自定义API分组限流
*
* @author Saint
*/
@Configuration
public class GatewayConfiguration1 {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration1(ObjectProvider<List<ViewResolver>> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 注入SentinelGatewayFilter
* Filter的优先级最高
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 注入限流异常处理器
* Filter的优先级最高
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 初始化限流规则
*/
@PostConstruct
public void doInit() {
initCustomizedApis();
initGatewayRules();
}
/**
* 自定义API分组限流,将/nacos/sentinel/**和/red/**进行统一分组,并提供name=saint_customized_api,然后在初始化网关限流规则时,针对该name设置
* 限流规则。同时,可以通过setMatchStrategy来设置不同path下的限流参数策略
*/
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
// 自定义针对API限流的ApiDefinition,apiName可以随便取,在GatewayFlowRule中填入即可。
ApiDefinition apiDefinition = new ApiDefinition("saint_customized_api");
// 匹配下面请求路径的请求将被限流
apiDefinition.setPredicateItems(new HashSet<ApiPredicateItem>() {
{
add(new ApiPathPredicateItem().setPattern("/nacos/sentinel/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
add(new ApiPathPredicateItem().setPattern("/red/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}
});
definitions.add(apiDefinition);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
/**
* 针对分组name来设置限流规则
*/
private void initGatewayRules() {
// 针对ApiDefinition进行限流
GatewayFlowRule rule = new GatewayFlowRule("saint_customized_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME).setCount(1)
.setIntervalSec(1);
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(rule);
GatewayRuleManager.loadRules(rules);
}
}
当我们访问/nacos/sentinel/**
和/red/**
路径时才会被限流,其他路径均不会。
Sentinel默认的异常处理器是com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler
,我们可以根据这个Handler自定义一个WebExceptionHandler实现类。
1> 自定义WebExceptionHandler实现类:
package com.saint.gateway.sentinel;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.function.Supplier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 异常处理器
*/
public class MySentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
private List<ViewResolver> viewResolvers;
private List<HttpMessageWriter<?>> messageWriters;
private final Supplier<ServerResponse.Context> contextSupplier = () -> {
return new ServerResponse.Context() {
public List<HttpMessageWriter<?>> messageWriters() {
return MySentinelGatewayBlockExceptionHandler.this.messageWriters;
}
public List<ViewResolver> viewResolvers() {
return MySentinelGatewayBlockExceptionHandler.this.viewResolvers;
}
};
};
public MySentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
/**
* 该方法的作用是,将限流的异常信息写回客户端
*/
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":999,\"msg\":\"访问人数太多了,让我歇歇吧\"}".getBytes();
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
} else {
return !BlockException
.isBlockException(ex) ? Mono.error(ex)
: this.handleBlockedRequest(exchange, ex).flatMap((response) -> this.writeResponse(response, exchange));
}
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
}
2> 将MySentinelGatewayBlockExceptionHandler注入到Spring容器中:
// 注入自定义的限流异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public MySentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new MySentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
接着根据API分组限流的案例进行验证;
响应的状态码是200,body是我们自定义的body:
{"code":999,"msg":"访问人数太多了,让我歇歇吧"}
sentinel提供了两种资源维度的限流:
本文的演示案例基于文章()
PS:Sentinel整合Nacos配置源:
spring:
cloud: #配置SpringCloudGateway的路由
sentinel:
transport:
dashboard: ip:port #sentinel控制台的请求地址
datasource:
ds1:
nacos:
server-addr: ip:port
data-id: ${spring.application.name}-${spring.profiles.active}-sentinel-gw-flow
group-id: DEFAULT_GROUP
data-type: json
# 流控规则
rule-type: gw-flow
ds2:
nacos:
server-addr: ip:port
data-id: ${spring.application.name}-${spring.profiles.active}-sentinel-gw-api-group
group-id: DEFAULT_GROUP
data-type: json
# api类型
rule-type: gw-api-group
eager: true #立即加载
log:
# 日志存放目录
dir: /data/sentinel/gateway
引入maven依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>