一、前言

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

在前面我们通过以下章节对Sentinel有了基础的了解:

Sentinel:分布式系统的流量防卫兵 | Spring Cloud 19

Sentinel:资源与规则定义 | Spring Cloud 20

Sentinel:原理深入浅出解读 | Spring Cloud 21

Sentinel:流量控制规则定义详解 | Spring Cloud 22

Spring Boot/Cloud集成Sentinel实现流量控制 | Spring Cloud 23

现在开始我们正式学习Sentinel在Spring Boot/Cloud中的集成使用。

书接上回:Spring Boot/Cloud集成Sentinel实现流量控制 | Spring Cloud 23 ,本节进行对以下部分进行集成演示:

  • SentinelFeign 集成
  • SentinelRestTemplate 集成
  • Spring Boot/Cloud集成 Sentinel 开启链路流控模式

二、项目集成 Sentinel

2.1 项目总体结构

spring boot red5 流媒体 示例 springboot 流量控制_spring boot

  • 模块sentinel-nacos-provider用于演示自定义限流处理逻辑
  • 模块sentinel-nacos-consumer用于演示SentinelFeign 集成
    用于演示SentinelRestTemplate集成
    用于演示Sentinel 链路流控模式

2.2 sentinel-nacos-provider模块

请见:Spring Boot/Cloud集成Sentinel实现流量控制 | Spring Cloud 23

2.3 sentinel-nacos-consumer模块

2.3.1 引入依赖

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

完整sentinel/sentinel-nacos-consumer/pom.xml

<?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>
        <groupId>com.gm</groupId>
        <artifactId>sentinel</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>sentinel-nacos-consumer</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-web-servlet</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <excludes>
                    <exclude>**/*.xlsx</exclude>
                    <exclude>**/*.xls</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.xlsx</include>
                    <include>**/*.xls</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

2.3.2 配置文件信息

完整版src/main/resources/bootstrap.yml

server:
  port: 3000

spring:
  application:
    name: @artifactId@
  cloud:
    nacos:
      username: @nacos.username@
      password: @nacos.password@
      discovery:
        server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos2kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos3.kc}:${NACOS_PORT:8848}
    sentinel:
      transport:
        port: 8720
        dashboard: 127.0.0.1:8080
      web-context-unify: false

feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

2.3.3 服务测试类

完整版 com/gm/sentinel_nacos_consumer/controller/ConsumerController.java

import com.gm.sentinel_nacos_consumer.service.ChainService;
import com.gm.sentinel_nacos_consumer.service.ProviderServiceFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ConsumerController {

    @Autowired
    ProviderServiceFeign providerServiceFeign;
    @Autowired
    ChainService chainService;
    @Autowired
    RestTemplate restTemplate;

    /**
     * 用于测试Feign支持
     *
     * @return
     */
    @RequestMapping(value = "sayHello", method = RequestMethod.GET)
    public String sayHello() {
        return providerServiceFeign.sayHello("hello world");
    }

    /**
     * 用于测试STRATEGY_DIRECT模式,根据调用方进行限流。
     *
     * @return
     */
    @RequestMapping(value = "sayHelloDirect", method = RequestMethod.GET)
    public String sayHelloDirect() {
        return chainService.message();
    }

    /**
     * 用于测试STRATEGY_CHAIN模式,根据调用链路入口限流。
     *
     * @return
     */
    @RequestMapping(value = "sayHelloChain", method = RequestMethod.GET)
    public String sayHelloChain() {
        return chainService.message();
    }

    /**
     * 用于restTemplate限流测试
     *
     * @return
     */
    @RequestMapping(value = "sayHelloRest", method = RequestMethod.GET)
    public String sayHelloRestTemplate() {
        String url = String.format("http://sentinel-nacos-provider/sayHello?world=hello world");
        String result = restTemplate.getForObject(url, String.class);
        return result;
    }
}

此类中的所需的依赖类,在下文中均有体现。

2.3.4 Sentinel 与 Feign 集成

加入 spring-cloud-starter-openfeign 依赖使 Sentinel starter 中的自动化配置类生效:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在配置文件中加入 SentinelFeign 的支持:

feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

新建简单使用示例com/gm/sentinel_nacos_consumer/service/ProviderServiceFeign.java

import com.gm.sentinel_nacos_consumer.config.FeignConfiguration;
import com.gm.sentinel_nacos_consumer.service.impl.ProviserServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "sentinel-nacos-provider", fallback = ProviserServiceFallback.class, configuration = FeignConfiguration.class)
public interface ProviderServiceFeign {

    @RequestMapping(value = "sayHello", method = RequestMethod.GET)
    String sayHello(@RequestParam("world") String world);

}

Feign 对应的接口中的资源名策略定义:httpmethod:protocol://requesturl @FeignClient 注解中的所有属性,Sentinel 都做了兼容。

ProviderServiceFeign接口中方法 sayHello对应的资源名为:GET:http://sentinel-nacos-provider/sayHello

新建FeignClient的配置类com/gm/sentinel_nacos_consumer/config/FeignConfiguration.java

import com.gm.sentinel_nacos_consumer.service.impl.ProviserServiceFallback;
import org.springframework.context.annotation.Bean;

public class FeignConfiguration {

    @Bean
    public ProviserServiceFallback proviserServiceFallback() {
        return new ProviserServiceFallback();
    }
}

新建熔断器的处理类com/gm/sentinel_nacos_consumer/service/impl/ProviserServiceFallback.java

import com.gm.sentinel_nacos_consumer.service.ProviderServiceFeign;

public class ProviserServiceFallback implements ProviderServiceFeign {
    @Override
    public String sayHello(String world) {
        return "fallback";
    }
}

先访问:http://127.0.0.1:3000/sayHello 测试功能是否正常访问:

spring boot red5 流媒体 示例 springboot 流量控制_ide_02


新建流控规则:

spring boot red5 流媒体 示例 springboot 流量控制_spring cloud_03

高频刷新:http://127.0.0.1:3000/sayHello ,验证Feign集成效果:

spring boot red5 流媒体 示例 springboot 流量控制_spring boot_04

2.3.5 Sentinel 与 RestTemplate 集成

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上 @SentinelRestTemplate 注解。

com/gm/sentinel_nacos_consumer/config/RestTemplateConfig.java

import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import com.gm.sentinel_nacos_consumer.component.ExceptionUtil;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@SentinelRestTemplate 注解的属性支持限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)的处理。

其中 blockHandlerfallback 属性对应的方法必须是对应 blockHandlerClassfallbackClass 属性中的静态方法。

该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor 方法一致,其中参数多出了一个 BlockException 参数用于获取 Sentinel 捕获的异常。

比如上述 @SentinelRestTemplate 注解中 ExceptionUtilhandleException 属性对应的方法声明如下,com/gm/sentinel_nacos_consumer/component/ExceptionUtil.java

import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;

public class ExceptionUtil {

    public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) throws Exception {
        ClientHttpResponse response = new SentinelClientHttpResponse("请求被限流!");
        return response;
    }

}

@SentinelRestTemplate 注解的限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)属性不强制填写

当使用 RestTemplate 调用被 Sentinel 熔断后,会返回 RestTemplate request block by sentinel 信息,或者也可以编写对应的方法自行处理返回信息。这里提供了 SentinelClientHttpResponse 用于构造返回信息

Sentinel RestTemplate 限流的资源规则提供两种粒度:

  • httpmethod:schema://host:port/path:协议、主机、端口和路径
  • httpmethod:schema://host:port:协议、主机和端口

http://sentinel-nacos-provider/sayHello对应的资源名有两种粒度,分别是:GET:http://sentinel-nacos-provider、GET:http://sentinel-nacos-provider/sayHello

因上节与Feign 集成测试过程中已对资源GET:http://sentinel-nacos-provider/sayHello创建流控规则:

spring boot red5 流媒体 示例 springboot 流量控制_sentinel_05

高频刷新:http://127.0.0.1:3000/sayHelloRest,验证RestTemplate集成效果:

spring boot red5 流媒体 示例 springboot 流量控制_spring boot_06

2.3.6 开启链路流控模式

Sentinel 1.6.3 版本开始,Sentinel Web filter 默认收敛所有 URL 的入口 context,因此链路流控不生效。1.7.0 版本开始通过配置开启链路流控。

引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-web-servlet</artifactId>
</dependency>

配置开启链路流控:

  • 方式一:
    在配置文件新增spring.cloud.sentinel.web-context-unify=false
  • 方式二:
    添加一个配置类,自己构建CommonFilter实例:
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterContextConfig {

    /**
     * @NOTE 在spring-cloud-alibaba v2.1.1.RELEASE及前,sentinel1.7.0及后,关闭URL PATH聚合需要通过该方式,spring-cloud-alibaba v2.1.1.RELEASE后,可以通过配置关闭:spring.cloud.sentinel.web-context-unify=false
     * 手动注入Sentinel的过滤器,关闭Sentinel注入CommonFilter实例,修改配置文件中的 spring.cloud.sentinel.filter.enabled=false
     * 入口资源聚合问题:https://github.com/alibaba/Sentinel/issues/1024 或 https://github.com/alibaba/Sentinel/issues/1213
     * 入口资源聚合问题解决:https://github.com/alibaba/Sentinel/pull/1111
     */
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
        return registration;
    }

}

新建com/gm/sentinel_nacos_consumer/service/ChainService.java

public interface ChainService {
    String message();
}

新建com/gm/sentinel_nacos_consumer/service/impl/ProviserServiceFallback.java

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.gm.sentinel_nacos_consumer.service.ChainService;
import org.springframework.stereotype.Service;

@Service
public class ChainServiceImpl implements ChainService {

    @SentinelResource(value = "message")
    public String message() {
        return "hello world";
    }
}

调用树如下图所示:

machine-root
                  /               \
                 /                 \
 /sayHelloDirect              /sayHelloChain
              /                       \
             /                         \
DefaultNode(message)         DefaultNode(message)

新建流控规则:

spring boot red5 流媒体 示例 springboot 流量控制_spring boot_07

使用@SentinelResource注解,将方法message()定义为资源

高频访问: http://127.0.0.1:3000/sayHelloChain,发现流控规则生效:

spring boot red5 流媒体 示例 springboot 流量控制_spring_08

在后续章节补充基于@SentinelResource注解的异常处理

控制台输出:

spring boot red5 流媒体 示例 springboot 流量控制_spring boot_09

高频访问:http://127.0.0.1:3000/sayHelloDirect,一切正常:

spring boot red5 流媒体 示例 springboot 流量控制_sentinel_10

开启链路流控模式后,针对资源message调用,链路入口/sayHelloChain的流控规则生效,对来自 /sayHelloDirect的调用漠不关心。

三、源码地址

源码地址:https://gitee.com/gm19900510/springboot-cloud-example