SpringCloud Gateway 使用 Sentinel 实现服务限流熔断

导读:本篇文章讲解 SpringCloud Gateway 使用 Sentinel 实现服务限流熔断,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.介绍

  • Alibaba Sentinel 支持对 Spring Cloud Gateway、Netflix Zuul 等主流的 API Gateway 进行限流与熔断配置。

  • 本文将介绍如何在 Spring Cloud Gateway 中使用 Alibaba Sentinel 进行限流配置,从而代替 Hystrix.

2.集成步骤

2.1. 首先需在Gateway网关模块引入以下依赖配置(以 Maven 为例):

  • 方案一: 只需引入 sentinel-spring-cloud-gateway-adapter 无需引入 spring-cloud-starter-alibaba-sentinel

    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        <version>x.y.z</version>
    </dependency>
    
  • 方案二: 如果需要在Sentinel 控制台管理 网关模块的限流,那么推荐这种配置方式

    如果想引入 spring-cloud-starter-alibaba-sentinel 那么就把上面那个 adapter 替换成 spring-cloud-alibaba-sentinel-gateway

    <!-- 没添加该依赖的话不能从application配置文件配置sentinel -->
     <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>
    

2.2 然后在使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。

@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}

2.3. 现在你可以选择在控制台中配置Sentinel限流规则 或者选择直接在Java代码中配置限流规则

2.3.1. 如果选择使用 Sentinel 控制台配置限流-熔断规则,那么还需要在 gateway 的配置文件中做出如下配置:

spring:
  cloud:
    # Sentinel 控制台连接配置
    sentinel:
      transport:
      	# 当前服务与控制台交互的端口号,默认为8719,同一个机器上若有多个应用于控制台交互时需要设置成不同的端口
       	port: 8739
        dashboard: 10.1.3.77:9090
      # 服务启动时直接建立心跳连接
      eager: true
      # Sentinel 储存规则的数据源配置(我这里使用的是Nacos来存储Sentinel的限流规则)
      datasource:
        ds:
          nacos:
            # Nacos 服务地址(可配置单点,或者集群的VIP地址)
            server-addr: 10.1.3.76:8848
            dataId: ${spring.application.name}-sentinel
            groupId: DEFAULT_GROUP
            namespace: sms-dev
            rule-type: flow
# Sentinel 控制台鉴权配置
sentinel:
  dashboard:
    auth:
      username: sentinel
      password: sentinel
  1. 此时你就可以在Sentinel控制台页面中配置限流规则,或者在你的 Nacos 配置中心中添加限流配置文件

    • sms-gateway-sentinel
      [
          {
              "resource": "api-service",
              "limitApp": "default",
              "grade": 1,
              "count": 2,
              "strategy": 0,
              "controlBehavior": 0,
              "clusterMode": false
          },
          {
              "resource": "url-proxy-1",
              "limitApp": "default",
              "grade": 1,
              "count": 2,
              "strategy": 0,
              "controlBehavior": 0,
              "clusterMode": false
          }
      ]
      

2.3.2 如果你选择在Java代码中配置限流规则

  • 下面是一个详细的配置示例
    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.callback.BlockRequestHandler;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
    import com.alibaba.csp.sentinel.slots.block.RuleConstant;
    import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
    import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
    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.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.codec.ServerCodecConfigurer;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.server.ServerResponse;
    import org.springframework.web.reactive.result.view.ViewResolver;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.PostConstruct;
    import java.util.*;
    
    /**
     * @description: 网关限流配置
     * @create: 2020-08-26 12:23
     **/
    @Configuration
    public class GatewaySentinelConfiguration {
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
    
        public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                            ServerCodecConfigurer serverCodecConfigurer) {
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }
    
    
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
            // Register the block exception handler for Spring Cloud Gateway.
            return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
        }
    
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public GlobalFilter sentinelGatewayFilter() {
            return new SentinelGatewayFilter();
        }
    
        /**
         * Spring 容器初始化的时候执行该方法
         */
        @PostConstruct
        public void doInit() {
            // 加载网关限流规则
            initGatewayRules();
            // 加载自定义限流异常处理器
            initBlockHandler();
        }
    
        /**
         * 网关限流规则
         * 建议直接在 Sentinel 控制台上配置
         */
        private void initGatewayRules() {
            Set<GatewayFlowRule> rules = new HashSet<>();
            /*
                resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称
                count:限流阈值
                intervalSec:统计时间窗口,单位是秒,默认是 1 秒
             */
            // rules.add(new GatewayFlowRule("order-service")
            //         .setCount(3) // 限流阈值
            //         .setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒
            // --------------------限流分组----------start----------
            rules.add(new GatewayFlowRule("url-proxy-1")
                    .setCount(1) // 限流阈值
                    .setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒
            rules.add(new GatewayFlowRule("api-service")
                    .setCount(5) // 限流阈值
                    .setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒
            // --------------------限流分组-----------end-----------
    
            // 加载网关限流规则
            GatewayRuleManager.loadRules(rules);
            // 加载限流分组
            initCustomizedApis();
    
            // ---------------熔断-降级配置-------------
            DegradeRule degradeRule = new DegradeRule("api-service") // 资源名称
                    .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) // 异常比率模式(秒级)
                    .setCount(0.5) // 异常比率阈值(50%)
                    .setTimeWindow(10); // 熔断降级时间(10s)
            // 加载规则.
            DegradeRuleManager.loadRules(Collections.singletonList(degradeRule));
        }
    
        /**
         * 自定义限流异常处理器
         */
        private void initBlockHandler() {
            BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
                @Override
                public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                    Map<String, String> result = new HashMap<>(3);
                    result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
                    result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
                    result.put("x","xx");
                    return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                            .contentType(MediaType.APPLICATION_JSON)
    //                        .body(BodyInserters.fromValue(result));
                            .body(BodyInserters.fromObject(result));
                }
            };
    
            // 加载自定义限流异常处理器
            GatewayCallbackManager.setBlockHandler(blockRequestHandler);
        }
    
        /**
         * 分组限流
         */
        private void initCustomizedApis() {
            //demo
            Set<ApiDefinition> definitions = new HashSet<>();
            // product-api 组
            ApiDefinition api1 = new ApiDefinition("product-api")
                    .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                        // 匹配 /product-service/product 以及其子路径的所有请求
                        add(new ApiPathPredicateItem().setPattern("/product-service/product/**")
                                .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    }});
    
            // order-api 组
            ApiDefinition api2 = new ApiDefinition("order-api")
                    .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                        // 只匹配 /order-service/order/index
                        add(new ApiPathPredicateItem().setPattern("/order-service/order/index"));
                    }});
            definitions.add(api1);
            definitions.add(api2);
            // 加载限流分组
            GatewayApiDefinitionManager.loadApiDefinitions(definitions);
        }
    }
    
    • Tips: 这种配置方式只适合固定的限流规则配置,如果需要灵活变动,那么建议使用上面那种方式

3. 总结

  • 如果 Gateway 用 Sentinel ,

      1. 建议在 Sentinel 控制台对 网关模块 进行具体的限流,熔断降级配置。 
    
      3. 否则还是推荐直接用 Gateway 内置的 RequestRateLimiter 跟 Hystrix 进行熔断限流配置。
    
  • 从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:

    • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
      * 在网关上用 Sentinel , 那么默认情况下一个 RouteId 对应的服务就是一个资源。

    • GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。

    • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

      • ApiDefinition:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
  • 官方Demo 示例:sentinel-demo-spring-cloud-gateway

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/15260.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!