SpringCloud学习——SpringCloud Alibaba Sentinel


官方文档:home (sentinelguard.io)

Sentinel 是什么?

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

Sentinel 的主要特性:

在这里插入图片描述


微服务搭建

jar 包启动:java -jar sentinel-dashboard-1.8.4.jar --server.port=8888

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
  port: 8401
spring:
  application:
    name: sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080  #sentinel dashboard地址
        #指定应用与sentinel控制台交互的端口,需要另启一个HttpServer【sentinel后台监控服务】
        port: 8719  
management:   #图形化监控
  endpoints:
    web:
      exposure:
        include: '*'
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelServiceApplication {

流量控制

  • 资源名:唯一名称,默认请求路径。

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

  • 阈值类型/单机阈值:

    • QPS(每秒钟的请求数量)︰当调用该API的QPS达到阈值的时候,进行限流。
    • 线程数:当调用该API的线程数达到阈值的时候,进行限流。
  • 是否集群:不需要集群。

  • 流控模式:

    • 直接:API达到限流条件时,直接限流。
    • 关联:当关联的资源达到阈值时,就限流自己。
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】。
  • 流控效果/手段:

    • 快速失败:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException
      • 这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
    • Warm up 预热:根据 Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
      • 该方式主要用于系统长期处于低水位的情况下,防止当流量突增时,直接把系统拉升到高水位瞬间把系统压垮的可能。
    • 排队等待:匀速排队,让请求以匀速的速度通过,对应的是漏桶算法。
      • 这种方式主要用于处理间隔性突发的流量,例如消息队列。在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

熔断降级

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

熔断策略

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
Sentinel 熔断规则属性
属性说明使用范围
资源名规则的作用对象。所有熔断策略
熔断策略Sentinel 支持3 中熔断策略:慢调用比例、异常比例、异常数策略。所有熔断策略
最大 RT请求的最大相应时间,请求的响应时间大于该值则统计为慢调用。慢调用比例
熔断时长熔断开启状态持续的时间,超过该时间熔断器会切换为探测恢复状态(HALF-OPEN),单位为 s。所有熔断策略
最小请求数熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)。默认为 5。所有熔断策略
统计时长熔断触发需要统计的时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)。默认1000 ms。所有熔断策略
比例阈值分为慢调用比例阈值和异常比例阈值,即慢调用或异常调用占所有请求的百分比,取值范围 [0.0,1.0]。慢调用比例 、异常比例
异常数请求或调用发生的异常的数量。异常数

下图为配置慢调用比例的熔断规则:

在这里插入图片描述

当 1s(统计时长)内持续接收 5 个(最小请求数)请求,且这些请求的响应时间超过最大 RT 的比例超过比例阈值,则在接下的 1s(熔断时长)之内,对这个方法的调用都会自动地熔断,经过熔断时间后,会进入探测恢复状态。


Sentinel 熔断状态

Sentinel 熔断降级中共涉及 3 种状态,熔断状态的之间的转换过程如下图。

在这里插入图片描述

Sentinel 熔断降级中共涉及 3 种状态:

  • 熔断关闭状态 (CLOSED):处于关闭状态时,请求可以正常调用资源。
    • 触发条件:满足以下任意条件,Sentinel 熔断器进入熔断关闭状态:全部请求访问成功。单位统计时长(statIntervalMs)内请求数目小于设置的最小请求数目。未达到熔断标准,例如服务超时比例、异常数、异常比例未达到阈值。处于探测恢复状态时,下一个请求访问成功。
  • 熔断开启状态 (OPEN):处于熔断开启状态时,熔断器会一定的时间(规定的熔断时长)内,暂时切断所有请求对该资源的调用,并调用相应的降级逻辑使请求快速失败避免系统崩溃。
    • 触发条件:满足以下任意条件,Sentinel 熔断器进入熔断开启状态:单位统计时长内请求数目大于设置的最小请求数目,且已达到熔断标准,例如请求超时比例、异常数、异常比例达到阈值。处于探测恢复状态时,下一个请求访问失败。
  • 探测恢复状态 (HALF-OPEN):处于探测恢复状态时,Sentinel 熔断器会允许一个请求调用资源。则若接下来的一个请求成功完成(没有错误)则结束熔断,熔断器进入熔断关闭(CLOSED)状态;否则会再次被熔断,熔断器进入熔断开启(OPEN)状态。
    • 触发条件:在熔断开启一段时间(降级窗口时间或熔断时长,单位为 s)后,Sentinel 熔断器自动会进入探测恢复状态。

@SentinelResource 注解

@SentinelResource 注解是 Sentinel 提供的最重要的注解之一,它还包含了多个属性,如下表。

属性说明使用要求
value用于指定资源的名称-
blockHandler服务限流后会抛出 BlockException 异常,而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。 简单点说,该属性用于指定服务限流后的后续处理逻辑。blockHandler 函数访问范围需要是 public;返回类型需要与原方法相匹配;参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandler 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
blockHandlerClass若 blockHandler 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。不能单独使用,必须与 blockHandler 属性配合使用;该属性指定的类中的 blockHandler 函数必须为 static 函数,否则无法解析。
fallback用于在抛出异常(包括 BlockException)时,提供 fallback 处理逻辑。 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
fallbackClass若 fallback 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。不能单独使用,必须与 fallback 或 defaultFallback 属性配合使用;该属性指定的类中的 fallback 函数必须为 static 函数,否则无法解析。
defaultFallback默认的 fallback 函数名称,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。 默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。返回值类型必须与原函数返回值类型一致;方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。-

流控配置
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handlerException")
    public CommonResult byResource() {
        return new CommonResult(200,"按资源名称限流测试OK", new Payment(2020L,"serial001"));
    }
    public CommonResult handlerException(BlockException e) {
        return new CommonResult(444, e.getClass().getCanonicalName() + "\t 服务不可用");
    }
}

当资源名配置为 /rateLimit/byResource 时,流控触发时会返回 Sentinel 自带默认的限流处理信息。当资源名配置为 byResource 时,流控触发时会调用 blockHandler 方法。

上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加—个兜底的,那代码膨胀加剧。
  4. 全局统—的处理方法没有体现。

用户自定义限流处理

自定义限流处理类

public class CustomerBlockHandler {
    public static CommonResult handlerException1(BlockException e) {
        return new CommonResult(4444,"按用户自定义 handlerException---1");
    }
    public static CommonResult handlerException2(BlockException e) {
        return new CommonResult(4444,"按用户自定义 handlerException---2");
    }
}

RateLimitController

@GetMapping("/custom")
@SentinelResource(value = "custom", blockHandlerClass = CustomerBlockHandler.class,
        blockHandler = "handlerException1")
public CommonResult custom() {
    return new CommonResult(200,"按用户自定义", new Payment(2020L,"serial001"));
}

熔断降级

服务提供者 Provider 搭建

pom.xml

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>api-commons</artifactId>
        <version>${project.version}</version>
    </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>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yml

server:
  port: 8081
spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #配置Nacos地址
management:
  endpoints:
    web:
      exposure:
        include: '*'

SentinelPaymentController

@RestController
@RequestMapping("/sentinelPayment")
public class SentinelPaymentController {
    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();
    static {
        hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        return new CommonResult(200,"from mysql,serverPort:  " + serverPort, payment);
    }
}
服务消费者 Consumer 搭建

pom.xml

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>api-commons</artifactId>
        <version>${project.version}</version>
    </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>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yml

server:
  port: 81

spring:
  application:
    name: nacos-consumer-order
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848  #注册进Nacos
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719

CircleBreakerController

@RestController
@RequestMapping("/consumer")
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";
    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/fallback/{id}")
    @SentinelResource(value = "fallback") //没有配置
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(
                SERVICE_URL+"/sentinelPayment/paymentSQL/"+id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("该ID没有对应记录,空指针异常");
        }
        return result;
    }
}
只配置 fallback
@RestController
@RequestMapping("/consumer")
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";
    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "fallbackHandler")
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(
                SERVICE_URL+"/sentinelPayment/paymentSQL/"+id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException ("非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException ("该ID没有对应记录,空指针异常");
        }
        return result;
    }
    public CommonResult fallbackHandler(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(
            444, "兜底异常fallbackHandler, exception内容:"+e.getMessage(), payment);
    }
}
只配置 blockHandler
@RestController
@RequestMapping("/consumer")
public class CircleBreakerController {
    ......
        
    @RequestMapping("/fallback/{id}")
    @SentinelResource(value = "fallback", blockHandler = "blockHandler")
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        ......
    }
    public CommonResult blockHandler(@PathVariable Long id, 
                                     BlockException e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(
            445,"blockHandler, blockException:"+e.getMessage(),payment);
    }
}

sentinel 控制台配置

在这里插入图片描述

总结:

  • fallback 管 Java 运行异常
  • blockHandler 管 sentinel 控制台配置的违规异常
fallback 和 blockHandler 都配置

若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑,即 blockHandler 优先级更高。

@SentinelResource(value = "fallback", fallback = "fallbackHandler", 
                  blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {

Sentinel 服务熔断 OpenFeign

消费者配置

pom.xml

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

application.yml

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider
  
# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

主启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {

feign 接口

@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value = "/sentinelPayment/paymentSQL/{id}")
    CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

自定义 fallback 类

@Component
public class PaymentFallbackService implements PaymentService{
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(4444,
                "服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

controller

//========OpenFeign=========
@Resource
public PaymentService paymentService;
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
    return paymentService.paymentSQL(id);
}

测试81调用8081,此时故意关闭8081微服务提供者,81消费侧自动降级,不会被耗死。


熔断框架比较

-SentinelHystrix
隔离策略信号量隔离(并发线程数限流)线程池隔商/信号量隔离
熔断降级策略基于响应时间、异常比率、异常数基于异常比率
实时统计实现滑动窗口(LeapArray)滑动窗口(基于RxJava)
动态规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持支持支持
限流基于QPS,支持基于调用关系的限流有限的支持
流量整形支持预热模式匀速器模式、预热排队模式不支持
系统自适应保护支持不支持
控制台提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等简单的监控查看

持久化

<!--sentinel持久化-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
server:
  port: 8401

spring:
  application:
    name: sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080  #sentinel dashboard地址
        port: 8719  
      datasource: #添加Nacos数据源配置,sentinel持久化
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:   #图形化监控
  endpoints:
    web:
      exposure:
        include: '*'

添加 Nacos 业务规则配置

在这里插入图片描述

流量控制配置

[{
    "resource": "/rateLimit/byUrl",
    "IimitApp": "default",
    "grade": 1,
    "count": 1, 
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
}]
  • resource:资源名称;
  • limitApp:来源应用;
  • grade:阈值类型,0表示线程数, 1表示QPS;
  • count:单机阈值;
  • strategy:流控模式,0表示直接,1表示关联,2表示链路;
  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
  • clusterMode:是否集群。

熔断配置

[{
  "resource": "payment-gary",
  "count": 1000,
  "timeWindow": 5,
  "grade": 0,
  "minRequestAmount": 2,
  "slowRatioThreshold": 0.2,
  "statIntervalMs": 10000
}]
参数名描述属性类型枚举项说明
resource资源名称字符——资源名称,在网关应用中指scg的routes:id
grade熔断策略数字0慢调用比例,不设置此为默认值
1异常比例
2异常数
count数字——
slowRatioThreshold比例阈值数字——熔断策略是慢调用时存在,单位是0.0-1.0的小数
timeWindow熔断时长数字——熔断规则所要熔断全部请求的时间范围,单位是秒
minRequestAmount最小请求数数字——熔断时必须达到的最小请求数量
statIntervalMs统计时长数字——熔断规则所统计的时间范围,单位毫秒

count 参数说明:当熔断策略是慢调用时,此值为最大 RT,单位是毫秒;当熔断是策略是异常比例时,此值为异常比例,单位是 0.0-1.0 之间的小数;当熔断策略是异常数时,此值为异常数目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值