文章目录
- 一、前言
- 二、Gateway集成Sentinel API
- 0、集成Sentinel的核心概念
- 1)GatewayFlowRule 和 ApiDefinition
- 2)GatewayFlowRule字段解释
- 1、针对Route维度限流
- 验证
- 2、针对API维度限流
- 验证
- 3、自定义限流异常返回值
- 验证
- 三、总结
一、前言
至此微服务网关系列文章已出:
- 【云原生&微服务>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.2</spring-boot.version><spring-cloud.version>2020.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
二、Gateway集成Sentinel API
sentinel服务1.6.0以上的版本可支持整合到网关进行统一流控:
sentinel提供了两种资源维度的限流:
- route维度:在配置文件中配置路由,资源名为对应的 routeId,一般是对某个微服务进行限流;这种维度属于粗粒度的限流。
- 自定义API维度:通过Sentinel 提供的API来自定义一些API分组,针对某一类的uri进行匹配限流,可以跨多个微服务;这种维度属于细粒度的限流。
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>
0、集成Sentinel的核心概念
整体集成原理如下:
1)GatewayFlowRule 和 ApiDefinition
网关的限流规则GatewayFlowRule:
- 针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流;
用户自定义的API分组ApiDefinition: - ApiDefinition可以看做是一些 URL 匹配的组合;限流的时候可以针对这个自定义的 API 分组进行限流。
2)GatewayFlowRule字段解释
网关限流规则 GatewayFlowRule 的字段如下:
- resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
- resourceMode:规则是针对 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)还是用户在 Sentinel 中定义的 API 分组(RESOURCE_MODE_CUSTOM_API_NAME),默认是 route。
- grade:限流指标维度,同限流规则的 grade 字段。
- count:限流阈值
- intervalSec:统计时间窗口,单位是秒,默认是 1 秒。
- controlBehavior:流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
- burst:应对突发请求时额外允许的请求数目。
- maxQueueingTimeoutMs:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
- paramItem:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。其中的字段:
- parseStrategy:从请求中提取参数的策略,目前支持提取来源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 参数(PARAM_PARSE_STRATEGY_URL_PARAM)四种模式。
- fieldName:若提取策略选择 Header 模式或 URL 参数模式,则需要指定对应的 header 名称或 URL 参数名称。
- pattern:参数值的匹配模式,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。(1.6.2 版本开始支持)
- matchStrategy:参数值的匹配策略,目前支持精确匹配(PARAM_MATCH_STRATEGY_EXACT)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS)和正则匹配(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本开始支持)
可以通过GatewayRuleManager.loadRules(rules) 手动加载网关规则、 或 通过 GatewayRuleManager.register2Property(property) 注册规则(推荐方式);
特别注意:当使用 Spring Cloud Alibaba Sentinel 数据源模块时,需要注意网关流控规则数据源类型是 gw-flow,若将网关流控规则数据源指定为 flow 则不生效。
application.yml配置文件:
spring:application:name: nacos-gatewaycloud:nacos:discovery:server-addr: 106.15.139.143:8848gateway:discovery:locator:# 开启从注册中心动态创建路由的功能enabled: true# 是否使用service-id的小写,默认是大写lower-case-service-id: trueroutes:- id: gateway-nacos-service-route# 其中配置的lb://表示从注册中心获取服务,后面的gateway-nacos-provider表示目标服务在注册中心上的服务名uri: lb://gateway-nacos-providerpredicates:- Path=/nacos/sentinel/** #被限流filters:- StripPrefix=2# 自定义Sentinel API分组限流- id: red_routeuri: lb://gateway-nacos-providerpredicates:- Path=/red/** #被限流filters:- StripPrefix=1- id: green_routeuri: lb://gateway-nacos-providerpredicates:- Path=/green/** #不被限流filters:- StripPrefix=1
下面的三个样例均依赖此application.yml配置文件。
1、针对Route维度限流
搞一个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 */@Configurationpublic 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);}/** * 初始化限流规则 */@PostConstructpublic 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"}
2、针对API维度限流
自定义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 */@Configurationpublic 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);}/** * 初始化限流规则 */@PostConstructpublic 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/**
路径时才会被限流,其他路径均不会。
3、自定义限流异常返回值
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提供了两种资源维度的限流:
- route维度:在配置文件中配置路由,资源名为对应的 routeId,一般是对某个微服务进行限流;这种维度属于粗粒度的限流。
- 自定义API维度:通过Sentinel 提供的API来自定义一些API分组,针对某一类的uri进行匹配限流,可以跨多个微服务;这种维度属于细粒度的限流。
本文的演示案例基于文章()
PS:Sentinel整合Nacos配置源:
spring:cloud: #配置SpringCloudGateway的路由sentinel:transport:dashboard: ip:port #sentinel控制台的请求地址datasource:ds1:nacos:server-addr: ip:portdata-id: ${spring.application.name}-${spring.profiles.active}-sentinel-gw-flowgroup-id: DEFAULT_GROUPdata-type: json# 流控规则rule-type: gw-flowds2:nacos:server-addr: ip:portdata-id: ${spring.application.name}-${spring.profiles.active}-sentinel-gw-api-groupgroup-id: DEFAULT_GROUPdata-type: json# api类型rule-type: gw-api-groupeager: true #立即加载log:# 日志存放目录dir: /data/sentinel/gateway
引入maven依赖:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId></dependency>