前面使用过Sentinel组件对服务提供者、服务消费者进行流控、限流等操作。除此之外,Sentinel还支持对Gateway、Zuul等主流网关进行限流。
自sentinel1.6.0版开始,Sentinel提供了Gateway的适配模块,能针对路由(route)和自定义API分组两个维度进行限流。
路由维度是指配置文件中的路由条目,资源名是对应的routeId,相比自定义API维度,这是比较粗粒度的。看下如何实现:
导入Sentinel组件为Gateway提供的适配依赖包,在pom.xml中导入依赖
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
dependency>
增加配置类SentinelRouteConfiguration
,实例化SentinelGatewayFilter
和SentinelBlockExceptionHandler
对象,初始化限流规则
@Configuration // 配置类
public class SentinelRouteConfiguration { // 路由限流规则配置类
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelRouteConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@PostConstruct
public void initGatewayRules() { // 初始化限流规则
Set<GatewayFlowRule> rules = new HashSet<>();
GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("user_route"); // 资源名(gateway中的路由id)
gatewayFlowRule.setCount(1); // 限流阈值
gatewayFlowRule.setIntervalSec(1); // 统计时间窗口,默认1s
rules.add(gatewayFlowRule);
GatewayRuleManager.loadRules(rules); // 载入规则
}
@PostConstruct
public void initBlockHandlers() { // 限流后的响应
BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
Map<String, Object> result = new HashMap<>();
result.put("code", "0");
result.put("message", "您已被限流");
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromObject(result));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler); // 设置限流响应
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() { // 初始化限流过滤器
return new SentinelGatewayFilter();
}
}
注意:Gateway限流是通过Filter实现的,主要是注入SentinelGatewayFilter实例和SentinelGatewayBlockExceptionHandler实例
在yml中,设置两个route,user_route
和shop_route
,上面主要是对user_route
限流了,着重看下
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
- id: shop_route # 路由id,唯一即可,默认UUID
uri: lb://shop # 路由地址(匹配成功后的服务地址) shop是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/shop/** # 断言,匹配规则
启动服务开始调试
成功完成路由级别的限流,那么后面看看API维度
的限流
上面那种限流方式可以看出灵活性不够高。自定义的API维度可以利用Sentinel提供的API自定义分组来进行限流。相比路由维度,这是一种更加细粒度的限流方式。
导入Gateway的适配包
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
dependency>
依然是实例化SentinelGatewayFilter
和SentinelBlockExceptionHandler
对象,初始化限流规则,定义API分组
@Configuration // 配置类
public class SentinelRouteConfiguration { // 路由限流规则配置类
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelRouteConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@PostConstruct
public void initGatewayRules() { // 初始化限流规则
Set<GatewayFlowRule> rules = new HashSet<>();
GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("user_api"); // 资源名,api分组的名称(自定义)
gatewayFlowRule.setCount(1); // 限流阈值
gatewayFlowRule.setIntervalSec(1); // 统计时间窗口,默认1s
rules.add(gatewayFlowRule);
GatewayRuleManager.loadRules(rules); // 载入规则
}
@PostConstruct
public void initCustomizedApis() {
Set<ApiDefinition> apiDefinitions = new HashSet<>();
ApiDefinition apiDefinition = new ApiDefinition("user_api") // 定义 api分组
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem()
.setPattern("/user/group/**") // 路径匹配规则
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); // 匹配策略,前缀匹配
}});
apiDefinitions.add(apiDefinition);
GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions); // 载入API分组定义
}
@PostConstruct
public void initBlockHandlers() { // 限流后的响应
BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
Map<String, Object> result = new HashMap<>();
result.put("code", "0");
result.put("message", "您已被限流");
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromObject(result));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler); // 设置限流响应
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() { // 初始化限流过滤器
return new SentinelGatewayFilter();
}
}
唯一要注意的是:路由匹配规则如果是单一的一个具体接口,不是匹配符,那么后面的匹配策略就没有必要再去配置了(setMatchStrategy()方法)
定义一个/user/group/findById
接口
@RequestMapping("/user/group/findById")
public String findGById(@RequestParam("id") Integer id) {
return userInfo.getOrDefault(id, null);
}
启动调用测试
可以看到配置的没有问题,满足/user/group/**
规则的请求,有被限流到
Gateway默认没有超时的限制,也就是数据什么时候返回,就等待到什么时候。如果不想等待,可以增加对超时的配置
gateway:
httpclient:
connect-timeout: 5000 # 建立连接时间限制,单位毫秒
response-timeout: 4s # 响应时间的时间限制
尝试下,接口睡眠5s,再次调用
curl localhost:8083/user/group/findById?id=1
{"timestamp":"2023-09-02T00:59:47.184+00:00","path":"/user/group/findById","status":504,"error":"Gateway Timeout","requestId":"7f5ff558-1"}
被告知504,超时了~
涉及到前后端分离的跨域请求时,浏览器访问后端地址通常提示No Access-Control-Allow-Origin header is present on the requested resource
可以在gateway中增加跨域配置,或者前端去配置都可以,自行协商。
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
httpclient:
connect-timeout: 5000 # 建立连接时间限制,单位毫秒
response-timeout: 4s # 响应时间的时间限制
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*" # 允许的来源
allowedMethods: "*" # 允许的方法
allowedHeaders: "*" # 允许的请求头 *表示所有
通常情况下,也可以不是按照全部允许来做,按照你的项目实际开发需求搞就行了。