0.背景

Sentinel 是由阿里巴巴中间件团队开发的开源项目,是一种面向分布式微服务架构的轻量级高可用流量控制组件。Sentinel是对 Hystrix的进一步优化,和Sentinel的对比如下:

SentinelHystrix
隔离策略基于并发数线程池隔离/信号量隔离
熔断降级策略基于响应时间或失败比率基于失败比率
实时指标实现滑动窗口滑动窗口(基于 RxJava)
规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持即将发布支持
调用链路信息支持同步调用不支持
限流基于 QPS / 并发数,支持基于调用关系的限流不支持
流量整形支持慢启动、匀速器模式不支持
系统负载保护支持不支持
实时监控 API各式各样较为简单
控制台开箱即用,可配置规则、查看秒级监控、机器发现等完善
常见框架的适配Servlet、Spring Cloud、Dubbo、gRPC 等Servlet、Spring Cloud Netflix

Sentinel 分为两个部分:

核心库(Java 客户端): 不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
控制台(Dashboard): 基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

1.Sentinel的安装与使用

sentinel下载地址:https://github.com/alibaba/Sentinel/releases

如果上面下载比较慢的话,可以用下面的百度网盘:

链接: https://pan.baidu.com/s/1POekqsI9LsLxH-ZPbViIDQ?pwd=th4g

提取码: th4g

下载后,我们来到下载后的目录,在这个目录中 shift+右击 打开powershell窗口,然后输入下面的指令:

java -jar sentinel-dashboard-1.8.6.jar

注意,这个jar包用的8080端口,其他项目不要占用8080端口。

然后浏览器里输入 http://localhost:8080, 打开sentinel 窗口。

用户名和密码都是:sentinel。 登录成功,看到下面界面:

2.创建演示模块

我们在原来的项目上单独创建一个新模块用来演示sentinel的功能。

我们首先在 父模块引入sentinel

  <!-- sentinel依赖  -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
                <version>2021.0.5.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

然后我们创建一个新的module,命名为sentinel。POM文件参考如下:

<?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>s-cloud-alibaba</artifactId>
        <groupId>site.longkui</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sentinel</artifactId>

    <dependencies>
        <!--spring boot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--nacos服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--openfeign  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--引入sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    </dependencies>

</project>

在resources下创建application.yml文件,里面内容参考如下:

server:
  port: 8400 #服务的端口
#spring相关配置
spring:
  application:
    name: service-sentinel  #服务的名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        #应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer,
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

创建主启动类:

package site.longkui;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;


@EnableDiscoveryClient
@SpringBootApplication
public class sentinel {
    public static void main(String[] args) {
        SpringApplication.run(sentinel.class, args);
    }
}

创建controller层代码:

package site.longkui.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/sentinel")
public class SentinelController {

    @GetMapping("/testA")
    public String testA() {
        return "I'm testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "I'm testB";
    }

}

然后启动模块,首先看一下是否在nacos注册成功。

然后访问一下我们创建的测试接口: localhost:8400/sentinel/testA

然后我们访问sentinel控制台,可以看到正常监控

3.测试流控规则

我们点击 流控规则,创建一个新规则。

创建成功

对于上面出现的内容的解释如下:

1.资源名: 唯一名称,默认请求路径,表示对该资源进行流控

2.针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)

3.阈值类型/单击阈值:
QPS:(每秒钟的请求数量):当调用该api的QPS达到阈值时,进行限流
线程数:当调用该线程数达到阈值的时候,进行限流

4.是否集群:不需要集群

5.流控模式:
直接: api达到限流条件时,直接限流
关联: 当关联的资源达到阈值时,就限流自己
链路: 只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)

6.流控效果:
快速失败: 直接失败,抛异常
Warm Up: 根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFctor,经过预热时长,才达到设置的QPS阈值
排队等待: 匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
关于QPS和线程数的解释:
QPS是每秒钟的请求数量,而线程数是每秒钟的线程数。
举个银行的例子,银行要受理业务,如果按QPS来说,就是每秒让1个客户进门受理,按线程数来说,就是每秒可以让很多客户进门并且办理业务,但是银行只开了1个窗口办理业务

(1)流控模式—直接

我们上面创建的就是 直接模式,1秒钟只能访问一次。如果超过阈值,则直接快速失败。

上面的效果,我们首先慢点击一次,发现接口正常,然后快速点击,发现接口失败,最后超过1秒点击,发现接口又正常了。

(2)流控模式—关联

我们把上面的流控规则改为下面这样:

上面表示,当我们访问testA这个接口时,如果超过阈值,则testB进行限流。

效果如下:

(3)流控模式—链路

链路表示只从某一条路进行控制。比如下面这样。

假设我们创建一个CommonService,里面有个findGoods方法。controller层里testA和testB都能访问此方法,但是我们只对testA方法进行控制和限流。

@SentinelResource("goods")
    public void findGoods(){
        System.out.println("找到货物!");
    }

service层全部代码:

package site.longkui.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;

@Service
public class  CommonService {

    //默认情况下是不被Sentinel监控的,需要我们自己通过注解来标记要监控
     @SentinelResource("goods")
     public String findGoods(String id){
        return "找到货物"+id;
    }
}

controller层改成下面这样:

package site.longkui.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import site.longkui.service.CommonService;

@RestController
@RequestMapping("/sentinel")
public class SentinelController {
    @Autowired
    CommonService commonService;


    @GetMapping("/testA")
    public String testA() {
        String info = commonService.findGoods("1");
        return "I'm testA-" + info;
    }

    @GetMapping("/testB")
    public String testB() {
        String info = commonService.findGoods("2");
        return "I'm testB-" +info;
    }


}

然后我们需要修改一下application.yml文件,加上下面这样的代码,将其配置为 false 即可根据不同的URL 进行链路限流,如果不配置将不会生效。

  web-context-unify: false

然后新建 一个规则:

分别从/sentinel/testA和/sentinel/testB访问goods,看下是否进行限制。

可以看到,/sentinel/testA 接口访问findGoods方法时进行了限流操作,直接出现了Whitelabel Error Page,而/sentinel/testB接口访问findGoods方法一直刷新的情况下没有出现限流操作。

(4)流控效果—warm up

上面测试流控模式是结果都是直接失败,我们也来测试一下warm up效果。

warm up,一般指的是 冷/热启动方式,或者你理解成高速低速模式,就像你在开车一样,一开始低速行驶,然后突然把油门踩到底,那么有可能把车搞坏。正确的做法应该是一步一步提速。

我们新建一个接口来测试warm up

 @GetMapping("/testC")
    public String testC() {
        System.out.println(new Date());
        return "I'm testC";
    }

然后新建一个规则:

根据公式,我们可以得到开始的阈值是 9/3=3。也就是1秒3次。然后一开始一秒超过3次就会限流,在3秒之内慢慢提升到1秒9次。

效果如下:

可以看到,经过3秒余热后,再刷新也不能达到1秒9次,那接口返回就一直是正常的。

(5)流控效果—排队等待

排队等待效果会设定 一段时间并发阈值,这个方式会严格控制请求通过的间隔时间,也就是让请求均匀的通过,起到一个整形的作用。

我们新增一个流控规则如下:

上面表示2秒内至多能处理5个请求。

我们用接口测试工具,间隔100ms一次,也就是2秒超过了5次。然后我们看一下后台接口监控情况:

可以看到,在一段时间内的最大峰值就是5。

3.测试熔断规则

慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。


异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% – 100%。


异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

(1)测试慢调用比例

新建一个熔断规则,设置为慢调用比例:


表示1秒的QPS大于5 并且 这些请求的RT>0.1(即最大响应时间) 并且大于比例阈值0.1(10%)的时候触发熔断。且熔断持续2秒。

我们每隔100ms请求接口一次,查看效果:

后台监控效果:

(2)测试异常比例

我们修改测试代码:

Integer id=0;
    @GetMapping("/testC")
    public String testC() {
        id=id+1;
        if(id>5){
            id=0;
            throw  new RuntimeException("测试异常");
        }
        System.out.println(new Date());
        return "I'm testC";
    }

上面的代码表示当id大于5时,直接抛出异常,然后把id重置为0,此时如果没有使用熔断策略,那么再次请求这个接口应该是正常返回的,但是我们参考下面设置测试异常比例:

这个表示1秒内超过3秒就会出现异常。并且在达到熔断时长时恢复接口访问。

可以看到一开始接口访问正常,然后id累计到5开始抛出异常(同时id重置为0),此时访问页面显示的是Whitelabel Error Page (一闪而过),因为id重置为0的原因,此时应该 是访问正常,但是我们增加了熔断策略,当我们异常比例数据过高时,接口开始限流,出现Blocked by sentinel。经过熔断时长后,接口又可以正常访问了。

(3)异常数

异常数表示当单位统计时长内的异常数目超过阈值之后会自动进行熔断。和上面的测试过程没有太大区别,一个表示比例,一个表示数量。

4.测试热点规则

参数类型:是参数索引对应的类型。比如我们访问/sentinel/testC?id=1。这个id就是类型,比如常见的int、string等

参数值:就是对参数的某个值进行参数限流,比如name=tom,表示只对这个tom进行限流,其余的不限流。

限流阈值:在统计时长内超过设定设定值就会被限流。

参数索引:表示第一个参数,默认从下标0开始,不考虑参数名字。

我们新建一个接口参考如下:

 @SentinelResource("testD")
    @GetMapping("/testD")
    public String testD(@RequestParam("name") String name) {
        return "I'm testD---"+name;
    }

然后新建一个热点规则

其中,单机阈值设置10,表示testD这个接口不论参数是什么都有一个限制是10,在这个基础上,分别对两个参数进行限制,tom限制阈值是3,abc这个参数阈值设置是5。

我们分别测试一下:

可以看到,tom和abc分别进行了限制,但是ok这个参数没有进行限制。

5.系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 LOAD、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性

如果配置了系统规则,所有的接口都会被限流。