【Springcloud】Sentinel熔断和降级

【Springcloud】Sentinel熔断和降级

【一】基本介绍

【1】什么是熔断和降级

服务的稳定是公司可持续发展的重要基石,随着业务量的快速发展,一些平时正常运行的服务,会出现各种突发状况,而且在分布式系统中,每个服务本身又存在很多不可控的因素,比如线程池处理缓慢,导致请求超时,资源不足,导致请求被拒绝,又甚至直接服务不可用、宕机、数据库挂了、缓存挂了、消息系统挂了…对于一些非核心服务,如果出现大量的异常,可以通过技术手段,对服务进行降级并提供有损服务,保证服务的柔性可用,避免引起雪崩效应。

(1)服务熔断
一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施。

(2)服务降级
是在服务器压力陡增的情况下,利用有限资源,根据当前业务情况,关闭某些服务接口或者页面,以此释放服务器资源以保证核心任务的正常运行。

【2】为什么使用熔断和降级

在一个分布式系统里,一个服务依赖多个服务,可能存在某个服务调用失败,比如超时、异常等,需要保证在一个依赖出问题的情况下,不会导致整体服务失败。

【3】Sentinel熔断和降级

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

sentinel具有以下特征:
(1)丰富的应用场景
Sentinel承接了阿里巴巴近十年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围),消息削峰填谷,集群流量控制,实时熔断下游不可用应用等

(2)完美的实时监控
Sentinel同时提供实时的监控功能,您可以在控制台看到接入应用的单台机器秒级数据,甚至500台一下规模的集群的汇总运行情况

(3)广泛的开源生态
Sentinel提供开箱即用的与其他框架/库的整合模块,例如与SpringCloud,Dubbo,gRPC的整合,您只需要引入响应的依赖并进行简单的配置即可快速接入Sentinel

(4)完美的SPI扩展点
Sentinel提供简单易用的,完美的SPI扩展接口,可以通过实现扩展接口来快速定制逻辑,例如定制规则管理,适配动态数据源等

【4】核心概念

(1)资源
资源是 Sentinel 中最核心的概念之一,它可以是任何需要被保护的内容,如一个服务接口、一段代码逻辑等。在 Sentinel 里,我们可以将资源抽象为一个操作,使用唯一的资源名来标识。
(2)规则
规则是 Sentinel 进行流量控制和熔断降级的依据,主要包括流量控制规则、熔断降级规则、系统保护规则等。这些规则定义了资源在不同场景下的处理策略,例如允许的最大请求数、熔断的阈值等。
(3)Slot 链
Slot 链是 Sentinel 实现各种功能的核心组件,它由一系列的 Slot 组成,每个 Slot 负责不同的功能,如统计、规则检查等。请求在进入资源时会依次经过这些 Slot,每个 Slot 都会对请求进行相应的处理。

【5】工作流程

(1)资源调用:当业务代码调用被 Sentinel 保护的资源时,Sentinel 会拦截该调用。
(2)Slot 链处理:请求进入 Slot 链,依次经过各个 Slot 的处理。每个 Slot 会根据规则对请求进行判断,例如统计请求的数量、检查是否超过限流阈值等。
(3)规则检查:在 Slot 链处理过程中,会根据配置的规则对请求进行检查。如果请求符合规则,则允许继续执行;如果不符合规则,则会触发相应的处理逻辑,如限流、熔断等。
(4)业务逻辑执行:如果请求通过了所有的规则检查,业务逻辑会正常执行;如果触发了限流或熔断,会执行相应的降级逻辑。

【6】底层实现机制

(1)动态规则配置

(1)规则存储:Sentinel 支持多种规则存储方式,如内存、文件、远程配置中心(如 Nacos、Apollo 等)。规则会被存储在相应的存储介质中,方便管理和修改。
(2)规则推送:当规则发生变化时,Sentinel 可以通过配置中心将新的规则推送给各个客户端。客户端会实时更新本地的规则,保证规则的一致性。

(2)统计指标(滑动窗口算法)

(1)滑动窗口算法:Sentinel 使用滑动窗口算法来统计请求的指标,如请求数、响应时间等。滑动窗口将时间划分为多个小的时间窗口,每个时间窗口记录该时间段内的请求信息。通过不断滑动窗口,可以实时统计不同时间段内的请求指标。
(2)原子类和并发容器:为了保证统计数据的准确性和并发安全性,Sentinel 使用了 Java 的原子类(如 AtomicLong)和并发容器(如 ConcurrentHashMap)来存储和更新统计数据。

(3)限流实现(令牌桶算法)

(1)令牌桶算法:Sentinel 支持令牌桶算法进行限流。令牌桶算法的基本思想是系统以固定的速率向令牌桶中放入令牌,每个请求需要从令牌桶中获取一个或多个令牌才能被处理。如果令牌桶中没有足够的令牌,请求将被拒绝。
(2)漏桶算法:除了令牌桶算法,Sentinel 也支持漏桶算法。漏桶算法将请求看作是水滴,漏桶以固定的速率处理请求。如果请求的速率超过了漏桶的处理速率,多余的请求将被暂时存储在漏桶中,直到漏桶有能力处理它们。

(4)熔断降级实现

(1)熔断策略:Sentinel 提供了多种熔断策略,如基于错误比例、基于响应时间等。当资源的错误比例或平均响应时间超过设定的阈值时,会触发熔断机制。
(2)熔断状态机:Sentinel 使用状态机来管理熔断状态,主要包括三个状态:关闭状态(Closed)、打开状态(Open)和半开状态(Half-Open)。在关闭状态下,请求正常通过;当触发熔断条件时,进入打开状态,所有请求将被快速失败;在一段时间后,进入半开状态,允许部分请求通过,根据这些请求的执行结果决定是否恢复到关闭状态。

(5)与业务集成

(1)注解方式:Sentinel 提供了注解的方式来集成到业务代码中,通过在方法上添加 @SentinelResource 注解,可以方便地对方法进行资源保护。
(2)AOP 拦截:除了注解方式,Sentinel 还支持使用 AOP(面向切面编程)来拦截业务方法,对其进行流量控制和熔断降级处理。

【7】Sentinel和hystrix的关系

Sentinel 和 Hystrix 都是用于处理分布式系统中服务容错的工具,它们的关系主要体现在功能相似、目标相同,但在实现原理、应用场景和特点上有所不同。具体如下:

(1)相似点

(1)功能相似:两者都具有限流、熔断、降级等功能,旨在保护服务在高并发、故障等情况下的稳定性和可靠性,防止因个别服务出现问题而导致整个系统崩溃。
(2)目标相同:都是为了解决分布式系统中的服务容错问题,提高系统的可用性和弹性,确保在复杂的网络环境和高负载情况下,系统能够持续稳定地提供服务。

(1)不同点

(1)实现原理:Hystrix 主要基于线程池隔离和信号量隔离来实现资源隔离,通过监控服务的健康状况,如请求失败率、延迟等指标,当达到一定阈值时触发熔断。Sentinel 则基于滑动窗口算法进行流量统计,通过规则配置来实现限流、熔断和降级等功能,它支持多种流量控制策略,如直接拒绝、Warm Up、排队等待等。
(2)应用场景:Hystrix 适用于对响应时间要求较高,且能够容忍一定程度的请求丢失的场景。Sentinel 更适用于对流量控制精度要求较高,需要灵活配置各种流量控制策略的场景,尤其是在微服务架构中,对多个服务之间的调用关系进行精细化管理时更为适用。
(3)特点:Hystrix 的熔断机制较为简单直接,一旦触发熔断,所有请求都会被快速失败,直到熔断器进入半开状态进行试探性恢复。Sentinel 提供了更丰富的熔断和降级策略,如慢调用比例熔断、异常比例熔断等,并且可以根据不同的业务场景进行灵活配置。

(1)相互补充

在实际的微服务架构中,Sentinel 和 Hystrix 可以结合使用。例如,可以使用 Sentinel 进行流量控制和实时的细粒度限流,而使用 Hystrix 来处理更宏观的服务熔断和降级,通过线程池隔离等方式防止服务雪崩。这样可以充分发挥两者的优势,为分布式系统提供更全面、更强大的容错保护。

【二】下载方式

注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。

【1】Windows平台安装包下载

可以从https://github.com/alibaba/Sentinel/releases下载sentinel-dashboard-$version.jar包。

使用如下命令启动控制台:

java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\sentinel\sentinel-dashboard-1.8.0.jar

其中-Dserver.port=8718用于指定Sentinel控制台端口为8718,F:\software\sentinel\sentinel-dashboard-1.8.0.jar为下载的包路径地址。

如果觉得官网下载慢,可以使用我分享的网盘地址: https://pan.baidu.com/s/1E9J52g6uW_VFWY34fHL6zA 提取码: vneh

【2】打开控制台

Sentinel提供了一个可视化的操作平台,安装好之后,在浏览器中输入(http://localhost:8718 (opens new window))就可以访问了,默认的用户名和密码都是sentinel(我使用的是1.8.0版本)
在这里插入图片描述

【三】使用案例

【1】添加依赖

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

<!-- SpringBoot Web -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

【2】添加Sentinel配置

spring: 
  application:
    # 应用名称
    name: ruoyi-xxxx 
  cloud:
    sentinel:
      # 取消控制台懒加载
      eager: true
      transport:
        # 控制台地址
        dashboard: 127.0.0.1:8718

【3】添加TestUserController.java,模拟接口返回用户信息。

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

@RestController
public class TestUserController
{
    @GetMapping("/user/info")
    public Object info()
    {
        return "{\"username\":\"admin\",\"password\":\"admin123\"}";
    }
}

【4】启动服务

查看Sentinel控制台的请求数据

【四】定义资源

资源是Sentinel中的核心概念之一。我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。最常用的资源是我们代码中的Java方法。Sentinel提供了@SentinelResource注解用于定义资源,并提供了AspectJ的扩展用于自动定义资源、处理BlockException等。

【1】代码定义

@SentinelResource用于定义资源,并提供可选的异常处理和fallback配置项。

接口定义IUserService.java

/**
 * 用户接口
 * 
 * @author ruoyi
 */
public interface IUserService
{
    public Object selectUserByName(String username);
}

接口实现IUserServiceImpl.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/**
 * 用户实现
 * 
 * @author ruoyi
 */
@Service
public class IUserServiceImpl implements IUserService
{
    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate restTemplate()
    {
        return new RestTemplate();
    }

    @SentinelResource(value = "selectUserByName", blockHandler = "selectUserByNameBlockHandler", fallback = "selectUserByNameFallback")
    @Override
    public Object selectUserByName(String username)
    {
        return restTemplate.getForObject("http://localhost:9201/user/info/" + username, String.class);
    }

    // 服务流量控制处理,参数最后多一个 BlockException,其余与原函数一致。
    public Object selectUserByNameBlockHandler(String username, BlockException ex)
    {
        System.out.println("selectUserByNameBlockHandler异常信息:" + ex.getMessage());
        return "{\"code\":\"500\",\"msg\": \"" + username + "服务流量控制处理\"}";
    }

    // 服务熔断降级处理,函数签名与原函数一致或加一个 Throwable 类型的参数
    public Object selectUserByNameFallback(String username, Throwable throwable)
    {
        System.out.println("selectUserByNameFallback异常信息:" + throwable.getMessage());
        return "{\"code\":\"500\",\"msg\": \"" + username + "服务熔断降级处理\"}";
    }

}

测试接口请求TestUserController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestUserController
{
    @Autowired
    private IUserService userService;

    @GetMapping("/info/{username}")
    public Object info(@PathVariable("username") String username)
    {
        return userService.selectUserByName(username);
    }
}

【2】属性说明

@SentinelResource注解包含以下属性:

value:资源名称,必需项(不能为空)
entryType:资源调用方向,可选项(默认为EntryType.OUT)
resourceType:资源的分类
blockHandler:对应处理BlockException的函数名称
blockHandlerClass:处理类的Class对象,函数必需为static函数
fallback:用于在抛出异常的时候提供fallback处理逻辑
defaultFallback:用作默认的回退的方法
fallbackClass:异常类的Class对象,函数必需为static函数
exceptionsToTrace:异常类跟踪列表(默认为Throwable.class)
exceptionsToIgnore:排除掉的异常类型

注意:注解方式埋点不支持 private 方法。

【五】控制台操作

【1】流量规则

(1)控制台定义

选择流控规则,新增流控规则,填入对应信息
在这里插入图片描述

(1)资源名: 唯一名称,默认请求路径
(2)针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
(3)阈值类型/单机阈值:
1-QPS(每秒请求数量):当调用该api的QPS达到阈值的时候,进行限流
2-线程数:当调用该api的线程数达到阈值的时候,进行限流
(4)是否集群: 不需要集群
(5)流控模式:
1-直接:api达到限流条件时,直接限流
2-关联:当关联的资源达到限流阈值时,就限流自己
3-链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到峰值,就进行限流)【api级别的针对来源】
(6)流控效果:
1-快速失败:直接失败,抛异常
2-Warm Up:根据coldFactor(冷加载因子,默认3)的值,从阈值/coldFactor,经过预热时长,才达到设置的QPS阈值
3-排队等待:匀速排队,让请求以匀速通过,阈值类型必须设置为QPS,否则无效

(2)代码定义

理解上面规则的定义之后,我们可以通过调用FlowRuleManager.loadRules()方法来用硬编码的方式定义流量控制规则,比如:

private void initFlowQpsRule() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule(resourceName);
    // set limit qps to 20
    rule.setCount(20);
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setLimitApp("default");
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

(3)属性说明

流量控制规则(FlowRule)重要属性

resource:资源名,资源名是限流规则的作用对象
limitApp:流控针对的调用来源,若为 default 则不区分调用来源 default,代表不区分调用来源
grade:限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
count:限流阈值
strategy:调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队) 直接拒绝
clusterMode:是否集群限流 否

同一个资源可以同时有多个限流规则,检查规则时会依次检查。

从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。1.7.0版本开始(对应SCA 2.1.1.RELEASE),我们在CommonFilter引入了WEB_CONTEXT_UNIFY这个init parameter,用于控制是否收敛context。将其配置为false即可根据不同的URL进行链路限流。 参考:https://github.com/alibaba/sentinel/issues/1213

【2】降级规则

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

(1)控制台定义

选择降级规则,新增降级规则,填入对应信息。

在这里插入图片描述

(2)代码定义

private void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource(KEY);
    // set threshold RT, 10 ms
    rule.setCount(10);
    rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
    rule.setTimeWindow(10);
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

(3)属性说明

熔断降级规则(DegradeRule)重要属性

resource:资源名,即规则的作用对象
grade:熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例
count:慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow:熔断时长,单位为 s
minRequestAmount:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5
statIntervalMs:统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
slowRatioThreshold:慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

同一个资源可以同时有多个降级规则。

【3】动态配置规则

上面的规则配置,都是存在内存中的。即如果应用重启,这个规则就会失效,可以整合动态配置系统,如ZooKeeper、Nacos、Apollo等,动态地实时刷新配置规则。

(1)文件配置规则

Sentinel支持通过本地文件加载规则配置,使用方式如下(限流规则作为演示)

spring:
  cloud:
    sentinel:
      datasource:
        ds1:
          file:
            file: classpath:flowRule.json
            data-type: json
            rule-type: flow

flowRule.json对应com.alibaba.csp.sentinel.slots.block.flow.FlowRule各属性。

[
  {
    "resource": "selectUserByName",
    "count": 1,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0,
    "controlBehavior": 0
  }
]

(2)Nacos配置规则

当sentinel重新启动时,sentinel dashboard中原来的数据将会全部消失,这样就需要重新定义限流规则,无疑是不可取的。所以需要将sentinel中定义的限流规则保存到配置中心里面。

具体的实现方法如下:
(1)在nacos中定义自定义限流策略sentinel-ruoyi-xxxx

[
  {
    "resource": "selectUserByName",
    "count": 2,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0,
    "controlBehavior": 0
  }
]

(2)添加依赖

<!-- springcloud alibaba nacos config -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- sentinel datasource nacos -->
<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

(3)添加相关配置,sentinel下面的dataSource中配置nacos

spring: 
  application:
    # 应用名称
    name: ruoyi-xxxx 
  cloud:
    nacos:
      config:
        # 配置中心地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yml
        # 共享配置
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
    sentinel:
      # 取消控制台懒加载
      eager: true
      transport:
        # 控制台地址
        dashboard: 127.0.0.1:8718
      # nacos配置持久化
      datasource:
        ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: sentinel-ruoyi-gateway
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

(4)启动sentinel应用,可以看到我们在nacos中配置的限流规则

【六】RestTemplate 支持

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

RestTemplate添加@SentinelRestTemplate注解保护支持。

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

服务熔断处理类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)
    {
        exception.printStackTrace();
        return new SentinelClientHttpResponse("{\"code\":\"500\",\"msg\": \"服务流量控制处理\"}");
    }

    // 服务熔断降级处理
    public static ClientHttpResponse fallback(HttpRequest request, byte[] body, ClientHttpRequestExecution execution,
            BlockException exception)
    {
        exception.printStackTrace();
        return new SentinelClientHttpResponse("{\"code\":\"500\",\"msg\": \"服务熔断降级处理\"}");
    }
}

【七】OpenFeign 支持

其实不管是Hystrix还是Sentinel对于Feign的支持,核心代码基本上是一致的,只需要修改依赖和配置文件即可。

OpenFeign 是一个声明式的 WebService 客户端,用于简化服务间的调用。而 Sentinel 是阿里巴巴开源的流量控制、熔断降级工具。将 OpenFeign 和 Sentinel 整合,可以为服务间调用提供熔断、限流等保护机制。

【1】添加依赖

<!-- SpringCloud Alibaba Nacos -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

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

【2】openfeign开启sentinel支持

在 Spring Boot 主类上添加 @EnableFeignClients 注解来启用 OpenFeign。同时,在 application.yml 中配置开启 Sentinel 对 OpenFeign 的支持:

spring: 
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848  # 服务注册地址
	sentinel:
      transport:
        dashboard: localhost:8080  # Sentinel 控制台地址
    openfeign:
      sentinel:
        enabled: true  # 开启 Sentinel 对 OpenFeign 的支持

【3】测试用户服务类RemoteUserService.java

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * 用户服务
 * 
 * @author ruoyi
 */
@FeignClient(contextId = "remoteUserService", value = "ruoyi-system", fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService
{
    /**
     * 通过用户名查询用户信息
     *
     * @param username 用户名
     * @return 结果
     */
    @GetMapping(value = "/user/info/{username}")
    public Object getUserInfo(@PathVariable("username") String username);
}

【4】熔断回调类RemoteUserFallbackFactory.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import feign.hystrix.FallbackFactory;

/**
 * 用户服务降级处理
 * 
 * @author ruoyi
 */
@Component
public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserService>
{
    private static final Logger log = LoggerFactory.getLogger(RemoteUserFallbackFactory.class);

    @Override
    public RemoteUserService create(Throwable throwable)
    {
        log.error("用户服务调用失败:{}", throwable.getMessage());
        return new RemoteUserService()
        {
            @Override
            public Object getUserInfo(String username)
            {
                return "{\"code\":\"500\",\"msg\": \"用户服务熔断降级处理\"}";
            }
        };
    }
}

【5】配置 Sentinel 规则

可以通过 Sentinel 控制台或代码的方式配置限流、熔断等规则。以下是一个通过代码配置限流规则的示例:

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class SentinelConfig {
    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("ExampleServiceClient#getExample"); // 资源名,与 Feign 方法对应
        rule.setCount(1); // 限流阈值,每秒允许通过 1 个请求
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流类型,QPS 限流
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

【6】启动类扫描配置

@EnableFeignClients(basePackages = "com.ruoyi")

创建的类需要在resources\META-INF下的spring.factories配置加载自动装配类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  .....
  com.ruoyi.system.api.factory.RemoteUserFallbackFactory

【八】Gateway 支持

【1】添加依赖

<!-- Spring Cloud Gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

【2】配置 Sentinel 和 Gateway

在 application.yml 中进行如下配置:

spring:
  cloud:
    gateway:
      routes:
        - id: example_route
          uri: http://example-service:8081  # 目标服务地址
          predicates:
            - Path=/example/**  # 路由匹配规则
    sentinel:
      transport:
        dashboard: localhost:8080  # Sentinel 控制台地址
      scg:
        fallback:
          mode: response  # 熔断降级返回模式,可选择 response 或 redirect
          response-status: 503  # 熔断时返回的 HTTP 状态码
          response-body: 'Service unavailable due to Sentinel protection'  # 熔断时返回的响应体

【3】开启 Sentinel 对 Gateway 的支持

在 Spring Boot 主类上添加 @EnableSentinelGateway 注解(在 Spring Cloud Alibaba 2.2.6 及以上版本),开启 Sentinel 对 Gateway 的支持:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.alibaba.csp.sentinel.adapter.spring.cloud.gateway.EnableSentinelGateway;

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

【4】配置 Sentinel 规则

可以通过代码的方式配置 Sentinel 针对 Gateway 的规则,包括限流、熔断等规则。以下是一个配置限流规则的示例:

import com.alibaba.csp.sentinel.adapter.spring.cloud.gateway.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.spring.cloud.gateway.scg.SentinelGatewayConfig;
import com.alibaba.csp.sentinel.adapter.spring.cloud.gateway.scg.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.spring.cloud.gateway.scg.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class SentinelGatewayConfigurer implements CommandLineRunner {

    @Autowired
    private SentinelGatewayConfig sentinelGatewayConfig;

    @Override
    public void run(String... args) throws Exception {
        initGatewayRules();
        initBlockHandler();
    }

    private void initGatewayRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("example_route"); // 资源名,与路由 ID 对应
        rule.setCount(1); // 限流阈值,每秒允许通过 1 个请求
        rule.setGrade(1); // 限流类型,QPS 限流
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

    private void initBlockHandler() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
                return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                        .contentType(MediaType.TEXT_PLAIN)
                        .body(BodyInserters.fromValue("Request blocked by Sentinel"));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

    @Bean
    public SentinelGatewayFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter(sentinelGatewayConfig);
    }
}

【5】Gateway使用Sentinel和redis令牌桶的区别

(1)实现原理

(1)Sentinel
Sentinel 基于滑动窗口算法进行流量统计和控制。它将时间划分为多个小的时间窗口,每个窗口记录该时间段内的请求信息。通过不断滑动窗口,可以实时统计不同时间段内的请求指标。
当请求进入时,Sentinel 会根据配置的规则(如 QPS 限流、并发线程数限流等)对请求进行检查。如果请求超过了规则设定的阈值,就会触发限流逻辑,如快速失败、返回自定义错误信息等。
(2)Redis 令牌桶
Redis 令牌桶算法的核心思想是系统以固定的速率向令牌桶中放入令牌,每个请求需要从令牌桶中获取一个或多个令牌才能被处理。
Redis 用于存储令牌桶的状态信息,包括令牌数量、令牌生成速率等。当请求到来时,会从 Redis 中获取令牌,如果令牌桶中有足够的令牌,则允许请求通过,并减少相应数量的令牌;如果没有足够的令牌,请求将被拒绝。

(2)性能表现

(1)Sentinel
由于 Sentinel 是基于内存的限流方案,数据的读写操作都在内存中完成,因此具有较低的延迟和较高的吞吐量。对于高并发场景下的本地服务限流,Sentinel 能够快速响应请求,提供高效的限流服务。
但是,Sentinel 的限流规则和统计信息都存储在本地内存中,当服务实例进行水平扩展时,每个实例的限流状态是独立的,可能会导致整体限流效果不准确。
(2)Redis 令牌桶
Redis 是一个高性能的内存数据库,具有良好的并发处理能力。使用 Redis 作为令牌桶的存储介质,可以实现分布式环境下的统一限流,确保各个服务实例的限流状态一致。
然而,由于请求需要与 Redis 进行网络通信,会引入一定的网络延迟。特别是在高并发场景下,频繁的 Redis 读写操作可能会成为性能瓶颈。

(3)分布式支持

(1)Sentinel
Sentinel 本身支持集群模式,但在分布式环境下,每个服务实例的限流规则和统计信息是独立的,需要通过额外的配置和机制来实现分布式限流。例如,可以使用 Sentinel 的规则推送功能,将规则同步到各个服务实例。
但是,由于每个实例的统计信息是独立的,可能会出现不同实例之间限流状态不一致的问题,导致整体限流效果不够精确。
(2)Redis 令牌桶
Redis 令牌桶天生支持分布式环境,因为 Redis 是一个共享的存储系统,各个服务实例可以通过访问同一个 Redis 实例来获取令牌桶的状态信息。
这样可以确保在分布式环境下,所有服务实例的限流状态是一致的,实现精确的分布式限流。

(4)规则配置和管理

(1)Sentinel
Sentinel 提供了丰富的规则配置方式,包括控制台配置、代码配置和动态规则推送等。可以通过 Sentinel 控制台直观地配置和管理限流规则,也可以通过代码在应用启动时进行规则配置。
同时,Sentinel 支持动态规则推送,当规则发生变化时,可以实时将新规则推送给各个服务实例,实现规则的动态更新。
(2)Redis 令牌桶
Redis 令牌桶的规则配置通常需要通过代码来实现,需要手动编写逻辑来设置令牌生成速率、令牌桶容量等参数。
规则的管理相对复杂,需要自己实现规则的存储和更新机制,例如可以将规则存储在数据库中,当规则发生变化时,更新 Redis 中的令牌桶配置信息。

(5)适用场景

(1)Sentinel
适用于本地服务的限流和熔断降级,特别是在单体应用或微服务架构中,对单个服务实例的流量进行控制和保护。
对于需要快速响应和低延迟的场景,Sentinel 基于内存的实现方式能够提供更好的性能。
(2)Redis 令牌桶
适用于分布式系统的统一限流,特别是在多个服务实例需要共享限流规则和状态的场景下,Redis 令牌桶可以确保各个实例的限流效果一致。
对于需要精确控制流量的场景,如对 API 接口进行严格的 QPS 限制,Redis 令牌桶的分布式特性可以提供更准确的限流控制。

### Sentinel 熔断降级机制详解 #### 一、熔断降级概述 Sentinel 是一款用于流量控制服务容错的开源工具,旨在帮助开发者构建更加健壮的应用程序。通过配置不同的规则,Sentinel 可以实现对服务调用的熔断降级处理[^1]。 当某个资源(如接口或方法)在一段时间内频繁失败时,Sentinel 将会触发熔断机制,暂时停止对该资源的新请求,直到经过一定时间窗口后再尝试恢复访问。如果后续请求仍然不稳定,则继续保持熔断状态;反之则恢复正常工作流程[^5]。 #### 二、熔断降级原理 Sentinel熔断降级功能基于统计学上的异常检测算法来判断何时应该启动保护措施: - **慢调用比例策略**:当指定时间段内的响应时间超过设定阈值的比例达到一定程度时,就会触发熔断操作; - **错误率策略**:一旦某段时间内的错误次数占比超过了预设界限,也会激活相应的防护动作; - **并发线程数限制**:为了防止过多的任务堆积造成系统崩溃,还可以设置最大允许执行任务的数量作为额外的安全屏障[^2]。 这些策略共同作用于应用程序内部的不同层次上,从而有效地提高了系统的稳定性可靠性。 #### 三、配置方式 要启用 Sentinel熔断降级特性,通常需要完成以下几个步骤的操作: ##### 3.1 注册初始化函数 首先要在 `META-INF/services` 文件夹下创建名为 `com.alibaba.csp.sentinel.init.InitFunc` 的文件,并指明具体的类路径,例如 `com.example.demo.config.CustomDegradeRule` 来加载自定义逻辑。 ##### 3.2 定义规则集 接着利用 API 或者 Web 控制台界面为特定的目标对象制定详细的参数选项,比如限流模式下的 QPS 数量、排队等待时限等细节内容。对于熔断场景而言,则需关注如下几个方面: - 设定最小请求数目 - 指定持续观察周期长度 - 明确触发条件及其对应的阀值范围 ```java // Java代码示例:动态调整熔断规则 FlowRule rule = new FlowRule(); rule.setResource("getUserByRT"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(20); // 设置QPS上限为20次/秒 List<FlowRule> rules = Arrays.asList(rule); FlowRuleManager.loadRules(rules); DegradeRule degradeRule = new DegradeRule("getUserByRT") .setCount(1).setTimeWindow(10) .setMinRequestAmount(5).setStatIntervalMs(1000); List<DegradeRule> degradeRules = Collections.singletonList(degradeRule); DegradeRuleManager.loadRules(degradeRules); ``` 上述例子展示了如何针对名为 "getUserByRT" 的API接口实施基本形式的速率限制与性能退化管理方案[^4]。 #### 四、实际效果展示 假设存在这样一个业务场景——每当遇到网络延迟过高或者服务器端返回HTTP 5xx系列的状态码时,我们都希望立即中断当前事务并给出友好提示给前端用户而不是让其长时间处于挂起状态。此时就可以借助 Sentinel 提供的相关组件轻松达成这一目标。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值