spring cloud alibaba开发笔记十四(SpringCloud Alibaba Sentinel)

SpringCloud Alibaba Sentinel的概念

◆Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性

◆资源:可以是Java应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码

◆规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则;且所有规则可以动态实时调整

为什么需要限流

◆顾名思义,限流就是限制流量,控制系统的QPS,从而达到保护系统的目的

◆为什么 需要限流( 搞清楚这些,再去决定你的系统需不需要限流)

1.平台用户增长过快

2.热点事件、热点接口、热点Key 

3.恶意请求

常见的限流算法

1.计数器算法:最简单的一种,对请求的个数进行计数

这种算法就是指定规定时间内允许接收多少请求,但是这种算法有两个漏洞:

第一,假设每分钟允许100个请求,请求全部集中在第一个一分钟最后和第二个一分钟的开始,就会有瞬间两百个请求。

第一,所有的请求都发生在一瞬间。

2.漏桶算法

往桶中以任意速率流入水,以一定速率流出水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率;

3. 令牌桶算法

是对漏桶算法的改进,漏桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用

1.所有的请求在处理之前都需要拿到一个可用的令牌才会被处理

2.根据限流大小,设置按照一定的速率往桶里添加令牌

3.桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝

4.请求到达后需要获取令牌桶中令牌

5.令牌桶保持有最低限额,保证足够的限流

搭建SpringCloud Alibaba Sentinel控制台

◆Sentinel 提供一个轻量级 的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能

1.下载控制台Jar包: Releases · alibaba/Sentinel · GitHub

2. java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 -Dproject.name=shuai-sentinel-dashboard -jar sentinel-dashboard-1.8.3.jar

3.从Sentinel 1 .6.0起,Sentinel Dashboard引入了基本的登录功能,默认的用户名密码都是sentinel

SpringCloud Alibaba Sentinel控制台有哪些能力

◆查看机器列表以及健康情况:收集Sentinel 客户端发送的心跳包,用于判断机器是否在线

◆监控(单机和集群聚合):通过Sentinel客户端暴露的监控API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控

◆规则管理和推送:统一管理推送规则

SpringCloud Alibaba Sentinel的限流功能

SpringCloud Alibaba Sentinel流量控制

◆服务端需要根据系统的处理能力对流量进行控制,Sentinel作为一个调配器,可以根据需要把随机的请求调整成合适的形状

 SpringCloud Alibaba Sentinel流量控制方向

◆资源的调用关系,例如资源的调用链路,资源和资源之间的关系

◆运行指标, 例如QPS、线程池、系统负载等

◆控制的效果,例如直接限流、冷启动、排队等

基于硬编码应用 Sentinel 的限流功能

创建Sentinel的微服务

首先引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>e-commerce-springcloud</artifactId>
        <groupId>com.taluohui.ecommerce</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-sentinel-client</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!-- 模块名及描述信息 -->
    <name>e-commerce-sentinel-client</name>
    <description>Sentinel Client</description>

    <dependencies>
        <!-- 创建工程需要的两个依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- web 工程 -->
        <dependency>
            <groupId>com.taluohui.ecommerce</groupId>
            <artifactId>e-commerce-mvc-config</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <!--
        SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
        SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
     -->
    <build>
        <finalName>${artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

配置文件

server:
  port: 8100
  servlet:
    context-path: /ecommerce-sentinel-client

spring:
  application:
    name: e-commerce-sentinel-client # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
  cloud:
    nacos:
      # 服务注册发现
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
        server-addr: 1.15.247.9:8848
        #  server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
        namespace: 22d40198-8462-499d-a7fe-dbb2da958648
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator
    sentinel:
      # 配置 sentinel dashboard 地址
      transport:
        dashboard: 1.15.247.9:7777
        port: 8719 # 会在应用对应的机器上启动一个 Http Server, 该 Server 会与 Sentinel 控制台做交互
      # 服务启动直接建立心跳连接
      eager: true

# 暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

创建入口文件

/**
 * <h1>Sentinel 集成到 SpringCloud 工程中</h1>
 * */
@EnableDiscoveryClient
@SpringBootApplication
public class SentinelClientApplication {

    public static void main(String[] args) {

        SpringApplication.run(SentinelClientApplication.class, args);
    }
}

 最简单的流控

后面会将流控规则写在配置文件中

/**
 * <h1>流控规则硬编码的 Controller</h1>
 * */
@Slf4j
@RestController
@RequestMapping("/code")
public class FlowRuleCodeController {

    /**
     * <h2>初始化流控规则</h2>
     * */
    @PostConstruct
    public void init() {

        // 流控规则集合
        List<FlowRule> flowRules = new ArrayList<>();
        // 创建流控规则
        FlowRule flowRule = new FlowRule();
        // 设置流控规则 QPS, 限流阈值类型 (QPS, 并发线程数)
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 流量控制手段
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        // 设置受保护的资源
        flowRule.setResource("flowRuleCode");
        // 设置受保护的资源的阈值
        flowRule.setCount(1);
        flowRules.add(flowRule);

        // 加载配置好的规则
        FlowRuleManager.loadRules(flowRules);
    }

    /**
     * <h2>采用硬编码限流规则的 Controller 方法</h2>
     * */
    @GetMapping("/flow-rule")
    @SentinelResource(value = "flowRuleCode")
    public CommonResponse<String> flowRuleCode() {
        log.info("request flowRuleCode");
        return new CommonResponse<>(0, "", "shuai-ecommerce");
    }

实现兜底策略(这种方法的兜底方法必须和请求方法写在一个类中,不是很优雅)

    /**
     * <h2>采用硬编码限流规则的 Controller 方法</h2>
     * */
    @GetMapping("/flow-rule")
//    @SentinelResource(value = "flowRuleCode")
    @SentinelResource(value = "flowRuleCode", blockHandler = "handleException")
    public CommonResponse<String> flowRuleCode() {
        log.info("request flowRuleCode");
        return new CommonResponse<>(0, "", "shuai-ecommerce");
    }

    /**
     * <h2>当限流异常抛出时, 指定调用的方法</h2>
     * 是一个兜底策略
     * */
    public CommonResponse<String> handleException(BlockException exception) {
        log.error("has block exception: [{}]", JSON.toJSONString(exception.getRule()));
        return new CommonResponse<>(
                -1,
                "flow rule exception",
                exception.getClass().getCanonicalName()
        );
    }

将兜底策略写在独立的类中(兜底的方法一定要使用静态方法)

/**
 * <h1>自定义通用的限流处理逻辑</h1>
 * */
@Slf4j
public class QinyiBlockHandler {

    /**
     * <h2>通用限流处理方法</h2>
     * 这个方法必须是 static 的
     * */
    public static CommonResponse<String> qinyiHandleBlockException(BlockException exception) {

        log.error("trigger qinyi block handler: [{}], [{}]",
                JSON.toJSONString(exception.getRule()), exception.getRuleLimitApp());
        return new CommonResponse<>(
                -1,
                "flow rule trigger block exception",
                null
        );
    }
}

同时接口方法上的注解也需要改变

    /**
     * <h2>采用硬编码限流规则的 Controller 方法</h2>
     * */
    @GetMapping("/flow-rule")
//    @SentinelResource(value = "flowRuleCode")
//    @SentinelResource(value = "flowRuleCode", blockHandler = "handleException")
    @SentinelResource(
            value = "flowRuleCode", blockHandler = "qinyiHandleBlockException",
            blockHandlerClass = QinyiBlockHandler.class
    )
    public CommonResponse<String> flowRuleCode() {
        log.info("request flowRuleCode");
        return new CommonResponse<>(0, "", "imooc-qinyi-ecommerce");
    }

SpringCloud Alibaba Sentinel控制台使用可视化的方式进行配置

/**
 * <h1>基于 Sentinel 控制台配置流控规则</h1>
 * Sentinel 是懒加载的, 先去访问一下, 就可以在 Sentinel Dashboard 看到了
 * */
@Slf4j
@RestController
@RequestMapping("/dashboard")
public class RateLimitController {

    /**
     * <h2>在 dashboard 中 "流控规则" 中按照资源名称新增流控规则</h2>
     * */
    @GetMapping("/by-resource")
    @SentinelResource(
            value = "byResource",
            blockHandler = "qinyiHandleBlockException",
            blockHandlerClass = QinyiBlockHandler.class
    )
    public CommonResponse<String> byResource() {
        log.info("coming in rate limit controller by resource");
        return new CommonResponse<>(0, "", "byResource");
    }

    /**
     * <h2>在 "簇点链路" 中给 url 添加流控规则</h2>
     * */
    @GetMapping("/by-url")
    @SentinelResource(value = "byUrl")
    public CommonResponse<String> byUrl() {
        log.info("coming in rate limit controller by url");
        return new CommonResponse<>(0, "", "byUrl");
    }
}

在 dashboard 中 "流控规则" 中按照资源名称新增流控规则

 在 "簇点链路" 中给 url 添加流控规则

 SpringCloud Alibaba Sentinel的降级功能

◆Sentinel 支持对RestTemplate服务调用进行保护,实现流控降级和异常降级

 在配置文件中开启对@SentinelRestTemplate的支持

# 开启或关闭 @SentinelRestTemplate 注解
resttemplate:
  sentinel:
    enabled: true

Sentinel 对服务调用的保护

对RestTemplate 做一些包装

/**
 * <h1>开启服务间的调用保护, 需要给 RestTemplate 做一些包装</h1>
 * */
@Slf4j
@Configuration
public class SentinelConfig {

    /**
     * <h2>包装 RestTemplate</h2>
     * */
    @Bean
    @SentinelRestTemplate(
            fallback = "handleFallback", fallbackClass = RestTemplateExceptionUtil.class,
            blockHandler = "handleBlock", blockHandlerClass = RestTemplateExceptionUtil.class
    )
    public RestTemplate restTemplate() {
        return new RestTemplate();  // 可以对其做一些业务相关的配置
    }
}

RestTemplate 在限流或异常时的兜底方法

/**
 * <h1>RestTemplate 在限流或异常时的兜底方法</h1>
 * */
@Slf4j
public class RestTemplateExceptionUtil {

    /**
     * <h2>限流后的处理方法</h2>
     * */
    public static SentinelClientHttpResponse handleBlock(HttpRequest request,
                                                         byte[] body,
                                                         ClientHttpRequestExecution execution,
                                                         BlockException ex) {
        log.error("Handle RestTemplate Block Exception: [{}], [{}]",
                request.getURI().getPath(), ex.getClass().getCanonicalName());
        return new SentinelClientHttpResponse(
                JSON.toJSONString(new JwtToken("shuai-block"))
        );
    }

    /**
     * <h2>异常降级之后的处理方法</h2>
     * */
    public static SentinelClientHttpResponse handleFallback(HttpRequest request,
                                                            byte[] body,
                                                            ClientHttpRequestExecution execution,
                                                            BlockException ex) {
        log.error("Handle RestTemplate Fallback Exception: [{}], [{}]",
                request.getURI().getPath(), ex.getClass().getCanonicalName());
        return new SentinelClientHttpResponse(
                JSON.toJSONString(new JwtToken("qinyi-imooc-block"))
        );
    }
}

使用 Sentinel 保护 RestTemplate 服务间调用 

/**
 * <h1>使用 Sentinel 保护 RestTemplate 服务间调用</h1>
 * */
@Slf4j
@RestController
@RequestMapping("/sentinel-rest-template")
public class SentinelRestTemplateController {

    private final RestTemplate restTemplate;

    public SentinelRestTemplateController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    /**
     * <h2>从授权服务中获取 JwtToken</h2>
     * 1. 流控降级:
     * 是针对于簇点链路中的 http://127.0.0.1:7000/ecommerce-authority-center/authority/token
     * 2. 容错降级: 对于服务不可用时不能生效
     * */
    @PostMapping("/get-token")
    public JwtToken getTokenFromAuthorityService(
            @RequestBody UsernameAndPassword usernameAndPassword) {

        String requestUrl =
                "http://127.0.0.1:7000/ecommerce-authority-center/authority/token";
        log.info("RestTemplate request url and body: [{}], [{}]",
                requestUrl, JSON.toJSONString(usernameAndPassword));

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return restTemplate.postForObject(
                requestUrl,
                new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
                JwtToken.class
        );
    }
}

通过 Sentinel 实现对请求的熔断降级

首先将上面的RestTemplate的@SentinelRestTemplate注解注释掉

提供熔断降级策略

/**
 * <h1>Sentinel 回退降级的兜底策略</h1>
 * 都需要是静态方法
 * */
@Slf4j
public class QinyiFallbackHandler {

    /**
     * <h2>getTokenFromAuthorityService 方法的 fallback</h2>
     * */
    public static JwtToken getTokenFromAuthorityServiceFallback(
            UsernameAndPassword usernameAndPassword
    ) {
        log.error("get token from authority service fallback: [{}]",
                JSON.toJSONString(usernameAndPassword));
        return new JwtToken("imooc-qinyi-fallback");
    }

    /**
     * <h2>ignoreException 方法的 fallback</h2>
     * */
    public static JwtToken ignoreExceptionFallback(Integer code) {
        log.error("ignore exception input code: [{}] has trigger exception", code);
        return new JwtToken("imooc-qinyi-fallback");
    }
}

在controller中使用熔断降级策略

@SentinelResource中的exceptionsToIgnore = { NullPointerException.class },可以忽略一部分异常,让服务不走兜底策略,直接报错。

/**
 * <h1>Sentinel 提供容错降级的功能</h1>
 * */
@SuppressWarnings("all")
@Slf4j
@RestController
@RequestMapping("/sentinel-fallback")
public class SentinelFallbackController {

    /** 注入没有增强的 RestTemplate */
    private final RestTemplate restTemplate;

    public SentinelFallbackController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @PostMapping("/get-token")
    @SentinelResource(
            value = "getTokenFromAuthorityService",
            fallback = "getTokenFromAuthorityServiceFallback",
            fallbackClass = { QinyiFallbackHandler.class }
    )
    public JwtToken getTokenFromAuthorityService(
            @RequestBody UsernameAndPassword usernameAndPassword) {

        String requestUrl =
                "http://127.0.0.1:7000/ecommerce-authority-center/authority/token";
        log.info("RestTemplate request url and body: [{}], [{}]",
                requestUrl, JSON.toJSONString(usernameAndPassword));

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return restTemplate.postForObject(
                requestUrl,
                new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
                JwtToken.class
        );
    }

    /**
     * <h2>让 Sentinel 忽略一些异常</h2>
     * */
    @GetMapping("/ignore-exception")
    @SentinelResource(
            value = "ignoreException",
            fallback = "ignoreExceptionFallback",
            fallbackClass = { QinyiFallbackHandler.class },
            exceptionsToIgnore = { NullPointerException.class }
    )
    public JwtToken ignoreException(@RequestParam Integer code) {

        if (code % 2 == 0) {
            throw new NullPointerException("yout input code is: " + code);
        }

        return new JwtToken("qinyi-imooc");
    }
}

 Sentinel 对 OpenFeign 熔断降级的支持

首先在配置文件中添加对用的配置(这里只是最简单的openfeign的配置,如果需要更详细的配置openfeign,请参考前面openfeign的章节。

# 打开 Sentinel 对 Feign 的支持
feign:
  sentinel:
    enabled: true

引入依赖

        <!-- Sentinel 适配了 Feign, 可以实现服务间调用的保护 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

在入口文件中添加注解@EnableFeignClients

通过 Sentinel 对 OpenFeign 实现熔断降级

/**
 * <h1>通过 Sentinel 对 OpenFeign 实现熔断降级</h1>
 * */
@FeignClient(
        value = "e-commerce-imooc",
        fallback = SentinelFeignClientFallback.class
)
public interface SentinelFeignClient {

    @RequestMapping(value = "qinyi", method = RequestMethod.GET)
    CommonResponse<String> getResultByFeign(@RequestParam Integer code);
}

Sentinel 对 OpenFeign 接口的降级策略

/**
 * <h1>Sentinel 对 OpenFeign 接口的降级策略</h1>
 * */
@Slf4j
@Component
public class SentinelFeignClientFallback implements SentinelFeignClient {
    @Override
    public CommonResponse<String> getResultByFeign(Integer code) {
        log.error("request supply for test has some error: [{}]", code);
        return new CommonResponse<>(
                -1,
                "sentinel feign fallback",
                "input code: "+ code
        );
    }
}

OpenFeign 集成 Sentinel 实现熔断降级 

/**
 * <h1>OpenFeign 集成 Sentinel 实现熔断降级</h1>
 * */
@Slf4j
@RestController
@RequestMapping("/sentinel-feign")
public class SentinelFeignController {

    private final SentinelFeignClient sentinelFeignClient;

    public SentinelFeignController(SentinelFeignClient sentinelFeignClient) {
        this.sentinelFeignClient = sentinelFeignClient;
    }

    /**
     * <h2>通过 Feign 接口去获取结果</h2>
     * */
    @GetMapping("/result-by-feign")
    public CommonResponse<String> getResultByFeign(@RequestParam Integer code) {
        log.info("coming in get result by feign: [{}]", code);
        return sentinelFeignClient.getResultByFeign(code);
    }
}

Sentinel结合Nacos实现限流规则持久化

◆Sentinel Dashboard将规则保存在内存中,重启之后就会丢失,所以,考虑使用外部持久化方案

◆在Nacos中创建规则,Nacos会推送到客户端

◆Sentinel Dashboard也会从Nacos去获取配置信息

首先在nacos中添加新的配置

 这是一个json格式的数组

[
    {
        "resource": "byResource",
        "LimitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

还需要在项目中添加依赖

        <!-- Sentinel 使用 Nacos 存储规则 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

 配置数据源

      datasource:
        # 名称任意, 代表数据源
        ds:
          nacos:
            # NacosDataSourceProperties.java 中定义
            server-addr: ${spring.cloud.nacos.discovery.server-addr}
            dataId: ${spring.application.name}-sentinel
            namespace: ${spring.cloud.nacos.discovery.namespace}
            groupId: DEFAULT_GROUP
            data-type: json
            # 规则类型: com.alibaba.cloud.sentinel.datasource.RuleType
            # FlowRule 就是限流规则
            rule-type: flow

这时候在sentinel的工作台上就可以看到

 Gateway 集成 Sentinel 实现网关限流

一般情况下,所有的外部请求应该都是通过网关进入,所以限流也应该在网关部分进行。

首先在网关微服务中添加依赖

        <!-- 集成 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>

添加配置

spring:
  cloud:
    sentinel:
      eager: true
      transport:
        port: 8720
        dashboard: 127.0.0.1:7777

 启动sentinel,这次要-Dcsp.sentinel.app.type=1设置网关支持

java -Dcsp.sentinel.app.type=1 -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 -Dproject.name=e-commerce-gateway -jar sentinel-dashboard-1.8.3.jar

添加硬编码的方式来进行网关限流

(对"e-commerce-nacos-client"服务进行限流,每60秒只能进行3次访问QPS)

/**
 * <h1>Gateway 集成 Sentinel 实现限流</h1>
 * */
@Slf4j
@Configuration
public class SentinelGatewayConfiguration {
    /** 视图解析器 */
    private final List<ViewResolver> viewResolvers;
    /** HTTP 请求和响应数据的编解码配置 */
    private final ServerCodecConfigurer serverCodecConfigurer;

    /**
     * <h2>构造方法</h2>
     * */
    public SentinelGatewayConfiguration(
            ObjectProvider<List<ViewResolver>> viewResolversProvider,
            ServerCodecConfigurer serverCodecConfigurer
    ) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * <h2>限流异常处理器, 限流异常出现时, 执行到这个 handler</h2>
     * */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // 默认会返回错误 message, code 429
        return new SentinelGatewayBlockExceptionHandler(
                this.viewResolvers,
                this.serverCodecConfigurer
        );
    }

    /**
     * <h2>限流过滤器, 是 Gateway 全局过滤器, 优先级定义为最高</h2>
     * */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * <h2>初始化限流规则</h2>
     * */
    @PostConstruct
    public void doInit() {

        log.info("---------------------------------------------------");

        // 加载网关限流规则
        log.info("load sentinel gateway rules (code define)");
        initGatewayRules();

        log.info("---------------------------------------------------");
    }

    /**
     * <h2>硬编码网关限流规则</h2>
     * */
    private void initGatewayRules() {

        Set<GatewayFlowRule> rules = new HashSet<>();

        GatewayFlowRule rule = new GatewayFlowRule();
        // 指定限流模式, 根据 route_id 做限流, 默认的模式
        rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
        // 指定 route_id -> service id
        rule.setResource("e-commerce-nacos-client");
        // 按照 QPS 限流
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 统计窗口和限流阈值
        rule.setIntervalSec(60);
        rule.setCount(3);

        rules.add(rule);

        // 加载到网关中
        GatewayRuleManager.loadRules(rules);
    }
}

自定义限流异常处理器

上文中使用的是默认的异常处理器,我们可以通过自定义的异常处理器来返回一些我们需要的异常信息

    /**
     * <h2>初始化限流规则</h2>
     * */
    @PostConstruct
    public void doInit() {

        log.info("---------------------------------------------------");

        // 加载网关限流规则
        log.info("load sentinel gateway rules (code define)");
        initGatewayRules();

        // 加载自定义限流异常处理器
        initBlockHandler();

        log.info("---------------------------------------------------");
    }


    /**
     * <h2>自定义限流异常处理器</h2>
     * */
    private void initBlockHandler() {

        // 自定义 BlockRequestHandler
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange,
                                                      Throwable throwable) {

                log.error("------------- trigger gateway sentinel rule -------------");

                Map<String, String> result = new HashMap<>();
                result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
                result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
                result.put("route", "e-commerce-nacos-client");

                return ServerResponse
                        .status(HttpStatus.TOO_MANY_REQUESTS)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue(result));
            }
        };

        // 设置自定义限流异常处理器
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

硬编码网关限流分组

 针对单个或者指定几个url进行限流

    /**
     * <h2>硬编码网关限流规则</h2>
     * */
    private void initGatewayRules() {

        Set<GatewayFlowRule> rules = new HashSet<>();

        GatewayFlowRule rule = new GatewayFlowRule();
        // 指定限流模式, 根据 route_id 做限流, 默认的模式
        rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
        // 指定 route_id -> service id
        rule.setResource("e-commerce-nacos-client");
        // 按照 QPS 限流
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 统计窗口和限流阈值
        rule.setIntervalSec(60);
        rule.setCount(3);

        //rules.add(rule);

        // 限流分组, Sentinel 先去找规则定义, 再去找规则中定义的分组
        rules.add(
                new GatewayFlowRule("nacos-client-api-1")
                        .setCount(3).setIntervalSec(60)
        );
        rules.add(
                new GatewayFlowRule("nacos-client-api-2")
                        .setCount(1).setIntervalSec(60)
        );

        // 加载到网关中
        GatewayRuleManager.loadRules(rules);

        // 加载限流分组
        initCustomizedApis();
    }


    /**
     * <h2>硬编码网关限流分组</h2>
     * 1. 最大限制 - 演示
     * 2. 具体的分组
     * */
    private void initCustomizedApis() {

        Set<ApiDefinition> definitions = new HashSet<>();

        // nacos-client-api 组, 最大的限制
        ApiDefinition api = new ApiDefinition("nacos-client-api")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    // 模糊匹配 /imooc/ecommerce-nacos-client/ 及其子路径的所有请求
                    add(new ApiPathPredicateItem()
                            .setPattern("/imooc/ecommerce-nacos-client/**")
                            // 根据前缀匹配
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});

        // nacos-client-api-1 分组
        ApiDefinition api1 = new ApiDefinition("nacos-client-api-1")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem()
                            // 精确匹配 /imooc/ecommerce-nacos-client/nacos-client/service-instance
                            .setPattern("/imooc/ecommerce-nacos-client" +
                                    "/nacos-client/service-instance"));
                }});

        // nacos-client-api-2 分组
        ApiDefinition api2 = new ApiDefinition("nacos-client-api-2")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem()
                            // 精确匹配 /imooc/ecommerce-nacos-client/nacos-client/project-config
                            .setPattern("/imooc/ecommerce-nacos-client" +
                                    "/nacos-client/project-config"));
                }});

        definitions.add(api1);
        definitions.add(api2);

        // 加载限流分组
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }


通过本地文件实现网关限流

首先将硬编码的方式注释掉

    /**
     * <h2>初始化限流规则</h2>
     * */
    //@PostConstruct
    public void doInit() {
        ············
    }

添加配置规则gateway-flow-rule-sentinel.json

[
  {
    "resource": "e-commerce-nacos-client",
    "resourceMode": 0,
    "count": 3,
    "intervalSec": 60
  },
  {
    "resource": "nacos-client-api",
    "resourceMode": 1,
    "count": 1,
    "intervalSec": 60
  }
]

添加配置分组gateway-flow-rule-api-sentinel.json

[
  {
    "apiName": "nacos-client-api",
    "predicateItems": [
      {
        "pattern": "/imooc/ecommerce-nacos-client/nacos-client/project-config"
      },
      {
        "pattern": "/imooc/ecommerce-nacos-client/**",
        "matchStrategy": 1
      }
    ]
  }
]

在配置文件中添加两个配置

spring:
  cloud:
    sentinel:
      datasource:
        #通过本地文件方式, 基于服务级别的配置
        dsl.file:
          file: classpath:gateway-flow-rule-sentinel.json
          # 代表服务级别的限流, 一步步点进去看, 文件类型
          ruleType: gw-flow
        # 通过本地文件方式, 细粒度对指定 api 进行配置
        ds2.file:
          file: classpath:gateway-flow-rule-api-sentinel.json
          # 代表 API 分组, 一步步点进去看, 文件类型
          ruleType: gw-api-group

通过nacos实现网关动态限流

在nacos上新建两个配置,就是之前的本地文件的配置

 在配置文件中修改之前的数据源配置

spring:
  application:
    name: e-commerce-gateway
  cloud:
    nacos:
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
        server-addr: 1.15.247.9:8848 # Nacos 服务器地址
        namespace: 22d40198-8462-499d-a7fe-dbb2da958648
    #        metadata:
    #          management:
    #            context-path: ${server.servlet.context-path}/actuator
    sentinel:
      eager: true
      transport:
        port: 8720
        dashboard: 127.0.0.1:7777
      datasource:
#        #通过本地文件方式, 基于服务级别的配置
#        dsl.file:
#          file: classpath:gateway-flow-rule-sentinel.json
#          # 代表服务级别的限流, 一步步点进去看, 文件类型
#          ruleType: gw-flow
#        # 通过本地文件方式, 细粒度对指定 api 进行配置
#        ds2.file:
#          file: classpath:gateway-flow-rule-api-sentinel.json
#          # 代表 API 分组, 一步步点进去看, 文件类型
#          ruleType: gw-api-group
        # 集成 Nacos
        ds1:
          nacos:
            server-addr: ${spring.cloud.nacos.discovery.server-addr}
            namespace: ${spring.cloud.nacos.discovery.namespace}
            # 测试时, 看看 Nacos 中修改是否能让 dashboard 生效, 就把第二个 count 也修改为 3
            data-id: gateway-flow-rule-sentinel
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: gw-flow
        ds2:
          nacos:
            server-addr: ${spring.cloud.nacos.discovery.server-addr}
            namespace: ${spring.cloud.nacos.discovery.namespace}
            data-id: gateway-flow-rule-api-sentinel
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: gw-api-group

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值