Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard

一、安装sentinel

1.下载地址:sentinel v1.8.6

2.启动sentinel dashboard,执行以下命令:

java -Dcsp.sentinel.log.dir=D:\xxx\sentinel\logs -Dserver.port=9217 -Dcsp.sentinel.dashboard.server=localhost:9217 -Dcsp.sentinel.heartbeat.client.ip=localhost -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=sentinel -jar sentinel-dashboard-1.8.6.jar

参数解释:

-Dcsp.sentinel.log.dir:日志保存路径,默认:${user.home}/logs/csp/

-Dserver.port=9217:控制台服务端口,默认:8080

-Dcsp.sentinel.dashboard.server=localhost:9217:控制台访问端口,默认:8080

-Dcsp.sentinel.heartbeat.client.ip=localhost:客户端心跳ip,多网卡需要指定这个ip,否则启动后报错,可忽略

-Dproject.name=sentinel-dashboard:控制台显示名称

-Dsentinel.dashboard.auth.username=sentinel:控制台登录账号,默认:sentinel

-Dsentinel.dashboard.auth.password=sentinel:控制台登录密码,默认:sentinel

-jar sentinel-dashboard-1.8.6.jar:运行sentinel1.8.6 jar包

3.启动成功界面

Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard_第1张图片

4.登录控制台,登录账号和密码默认为sentinel,启动命令可自定义登录账号和密码。

Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard_第2张图片

二、Gateway网关pom.xml引入sentinel组件



    org.springframework.cloud
    spring-cloud-starter-gateway
    3.1.4




    com.alibaba.csp
    sentinel-spring-cloud-gateway-adapter
    1.8.6




    com.alibaba.csp
    sentinel-transport-simple-http
    1.8.6

三、注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler即可,参考官方文档:https://sentinelguard.io/zh-cn/docs/api-gateway-flow-control.html

@Configuration
public class GatewayConfiguration {

    private final List viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider> 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(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
        initCustomizedApis();
    }

    private void initCustomizedApis() {
        Set definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("some_customized_api")
            .setPredicateItems(new HashSet() {{
                add(new ApiPathPredicateItem().setPattern("/animal/**")
                    .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
            }});
        ApiDefinition api2 = new ApiDefinition("another_customized_api")
            .setPredicateItems(new HashSet() {{
                add(new ApiPathPredicateItem().setPattern("/system/**"));
            }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

private void initGatewayRules() {
        Set rules = new HashSet<>();
        rules.add(new GatewayFlowRule("system-route")
                .setCount(1) // 限流阈值
                .setIntervalSec(1)); // 统计时间窗口,单位是秒,默认是 1 秒

        rules.add(new GatewayFlowRule("animal-route")
                .setCount(1) // 限流阈值
                .setIntervalSec(1)); // 统计时间窗口,单位是秒,默认是 1 秒
        GatewayRuleManager.loadRules(rules);
    }
}

四、application.yaml配置路由

server:
  port: 9211
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        # Add your routes here.
        - id: system_route
          uri: lb://system
          predicates:
            - Path=/system/**
        - id: animal_route
          uri: lb://animal
          predicates:
            - Path=/animal/**

五、启动网关Gateway、服务,触发限流

Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard_第3张图片

正常访问

Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard_第4张图片

触发限流

六、嗯哼?sentinel dashboard没有显示嘛,怎么回事?

Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard_第5张图片

按官方文档说明,启动配置VM参数增加:

# 注:通过 Spring Cloud Alibaba Sentinel 自动接入的 API Gateway 整合则无需此参数

-Dcsp.sentinel.app.type=1

Spring Cloud Gateway集成Sentinel 1.8.6及Sentinel Dashboard_第6张图片

但是!!!好像也不行!!还必须增加一个参数,指向控制台地址:

-Dcsp.sentinel.dashboard.server=localhost:9001

注意:对网关发起请求后,需要等待大概10秒左右,才会在sentinel dashboard看到网关流控控制面板。

七、小小完善

1.第六节说到,需要增加启动配置,这里有4种解决方案。

方案一,启动类Application增加以下参数:

System.setProperty(SentinelConfig.APP_TYPE_PROP_KEY, "1");

System.setProperty("csp.sentinel.dashboard.server","localhost:9001");

System.setProperty(SentinelConfig.PROJECT_NAME_PROP_KEY,"gateway-dashboard");

方案二,启动配置VM增加如下参数:

-Dcsp.sentinel.app.type=1 -Dcsp.sentinel.dashboard.server=localhost:9001 -Dproject.name=gateway-dashboard

方案三,新增sentinel.properties配置文件,详情参阅com.alibaba.csp.sentinel.config.SentinelConfigLoader加载配置逻辑,配置内容如下:

project.name=gateway-dashboard # 控制台显示名称

csp.sentinel.app.type=1 # 指定类型为gateway网关类型

csp.sentinel.dashboard.server=localhost:9211 # 指定sentinel dashboard控制台地址

方案四,application.yaml增加自定义配置参数,再参照方案一

1.自定义限流全局异常

新增异常回调:

package com.akim.cloud.gateway.common.sentinel.handler;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

public class SentinelFallbackHandler implements WebExceptionHandler {
    private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{'code':429, 'msg':'系统繁忙,请稍后再试!'}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

    @Override
    public Mono handle(ServerWebExchange exchange, Throwable ex) {
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }
        if (!BlockException.isBlockException(ex)) {
            return Mono.error(ex);
        }
        return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
    }

    private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}

2.改造GatewayConfiguration

可以看出上面配置api限流和服务分组时,很不友好,直接读取application.yaml gateway配置的routes。

新增application.yaml自定义配置:

akim:
  # gateway网关限流
  sentinel:
    enabled: true # 是否开启网关限流,默认true
    count: 10 # 限流阈值,Double类型
    intervalSec: 1 # 统计时间窗口,单位:秒,Long类型,默认1秒

新增配置文件对象类SentinelProperties.java:

package com.akim.cloud.gateway.common.sentinel.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.validation.constraints.NotNull;

@Data
@ConfigurationProperties("akim.sentinel")
public class SentinelProperties {
    @NotNull(message = "限流阈值")
    private Double count;

    @NotNull(message = "统计时间窗口,单位:秒")
    private Long intervalSec;
}

b.注入全局异常拦截

c.直接上代码

package com.akim.cloud.gateway.common.sentinel.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.akim.cloud.gateway.common.sentinel.config.SentinelProperties;
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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.support.NameUtils;
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 javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@EnableConfigurationProperties(SentinelProperties.class)
@ConditionalOnProperty(prefix = "akim.sentinel", name = "enabled", matchIfMissing = true) // 如果配置文件属性值为false,则不注入
@Configuration
@Slf4j
public class GatewayConfiguration {
    private final List viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    protected final RouteDefinitionLocator routeDefinitionLocator;
    private final SentinelProperties sentinelProperties;

    public SentinelBlockHandler(ObjectProvider> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer,
                                RouteDefinitionLocator routeDefinitionLocator, SentinelProperties sentinelProperties) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
        this.routeDefinitionLocator = routeDefinitionLocator;
        this.sentinelProperties = sentinelProperties;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelFallbackHandler sentinelGatewayExceptionHandler() {
        return new SentinelFallbackHandler();
    }

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

    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
        // 限流api
        initCustomizedApis();
        // 服务分组流控
        initGatewayRules();
    }

    private void initCustomizedApis() {
        Set definitions = new HashSet<>();
        List routeDefinitions = routeDefinitionLocator.getRouteDefinitions().collectList().block();
        routeDefinitions.stream().forEach(route -> {
            PredicateDefinition pathDefinition = CollUtil.findOne(route.getPredicates(),
                    predicateDefinition -> "Path".equals(predicateDefinition.getName()));
            if (pathDefinition == null) {
                log.info("[sentinel][Route({}) 没有 Path 条件,忽略接口限流]", route.getId());
                return;
            }
            String path = pathDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0");
            if (StrUtil.isEmpty(path)) {
                log.info("[sentinel][Route({}) Path 的值为空,忽略接口限流]", route.getId());
                return;
            }
            definitions.add(new ApiDefinition(route.getId() + "-api")
                    .setPredicateItems(new HashSet() {
                        {
                            // 匹配 Path 前缀以及其子路径的所有请求
                            add(new ApiPathPredicateItem().setPattern(path)
                                    .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); // 匹配前缀,不设置则认为完全匹配
                        }
                    }));
        });
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    /**
     * 网关限流规则
     * 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和matchStrategy: 为后续参数匹配特性预留,目前末实现。
     */
    private void initGatewayRules() {
        Set rules = new HashSet<>();
        List definitions = routeDefinitionLocator.getRouteDefinitions().collectList().block();
        definitions.stream().forEach(route -> {
            if (StrUtil.isEmpty(route.getId())) {
                log.info("[sentinel][Route 没有 Id 条件,忽略接口限流]");
                return;
            }
            rules.add(new GatewayFlowRule(route.getId())
                    .setCount(sentinelProperties.getCount()) // 限流阈值
                    .setIntervalSec(sentinelProperties.getIntervalSec())); // 统计时间窗口,单位是秒,默认是 1 秒
        });
        // 加载网关限流规则
        GatewayRuleManager.loadRules(rules);
    }
}

搞定!

你可能感兴趣的:(Java,spring-cloud,sentinel,spring,spring,cloud)