如何使用Sentinel进行流量控制和熔断

网站流量控制和熔断

流量控制

通过对请求数量的限制,防止系统被过多的请求压垮。

流量控制的3个主要优势:

1、防止过载

2、避免雪崩效应

3、优化用户体验

常见的实现流量控制的2种方式:

1、限流:通过固定窗口、令牌桶或漏桶等算法限制单位时间内的请求数量

2、排队:当请求量超出处理能力时,部分请求进入等待队列,防止立即超载

熔断机制

目的是当我们的某个下游服务发生异常时,赶紧把这个服务隔离或降级,不让它一直发送失败请求,保证别的服务功能能正常使用,整个系统不会挂掉。

熔断的工作机制:

1、监控服务健康状态:系统会实时监控服务的调用情况,例如请求成功率、响应时间等,判断服务的健康状况。

2、进入熔断状态:当某个服务的错误率达到设定阈值(如响应时间过长或出错率过高)时,系统会 激活熔断器,暂时停止对该服务的调用,避免消耗不必要的资源和让错误进一步扩散。

3、快速失败:在熔断状态下,系统不会再等待超时,而是直接返回失败响应,减少系统资源占用,并避免因长时间等待导致用户体验的恶化(也可以降级处理)。

4、熔断恢复机制:熔断并非永久状态。在一段时间后,熔断器会进入 半开状态,允许少量请求测试服务的健康情况。如果恢复正常,熔断器将关闭,恢复正常服务调用;如果仍有问题,则继续保持熔断。

降级机制

某个服务响应能力下降或该服务不可用,提供一个简化版的功能或返回默认值作为兜底,保持系统的部分功能可用,确保用户的体验连续性,避免系统频繁报错。

熔断和降级一般是结合使用,先触发熔断再进行降级。

有损服务

就是"丢车保帅",在系统资源有限或负载较高的情况下,系统有意识的舍弃部分非核心功能,来确保系统整体的稳定和核心功能的可用性。

下面我以题库题目场景为例

1、查看题库列表接口限流熔断:

资源:listQuestionBankVOByPage 接囗

目的:控制对耗时较长的、经常访问的接口的请求频率,防止过多请求导致系统过载。

限流规则:

策略:整个接口每秒钟不超过 10 次请求

阻塞操作:提示"系统压力过大,请耐心等待

熔断规则:

熔断条件:如果接口异常率超过 10%,或者慢调用(响应时长>3秒)的比例大于 20%,触发 60 秒熔断。

熔断操作:直接返回本地数据(缓存或空数据)

2、单IP查看题目列表限流熔断:

资源:listQuestionVoByPage 接口

限流规则:

策略:每个IP 地址每分钟允许查看题目列表的次数不能超过 60 次。

阻塞操作:提示"访问过于频繁,请稍后再试

熔断规则:

熔断条件:如果接口异常率超过 10%,或者慢调用(响应时长>3秒)的比例大于 20%,触发 60 秒熔断。

熔断操作:直接返回本地数据(缓存或空数据)

Sentinel

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

sentinel的功能:

限流:支持基于 QPS、并发数量等条件的限流,支持滑动窗口、预热、漏桶等算法。

熔断降级:支持失败率、慢调用比例等指标触发熔断,并提供自动恢复机制。

热点参数限流:可以基于特定的参数进行限流,如限制特定用户ID的请求频率。

系统负载保护:可以根据系统的实际负载(如 CPU、内存)动态调整流量。

丰富的规则配置:通过配置中心或控制台动态调整限流和熔断规则。

优势:功能丰富、提供控制台、更新较频繁、社区活跃、文档清晰,能够快速入门上手。

sentinel主要是对资源进行保护,主要分为3步:定义资源、定义规则、校验规则是否生效

Sentinel配置

在项目中引入sentinel的依赖:

复制代码
<!--        sentinel-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>1.8.6</version>
        </dependency>

然后我们在zhimianguan目录下新建一个sentinel包,编写一个SentinelTest类试试sentinel使用:

复制代码
public class SentinelTest {
    public static void main(String[] args) {
        // 配置规则.
        initFlowRules();

        while (true) {
            // 1.5.0 版本开始可以直接利用 try-with-resources 特性
            try (Entry entry = SphU.entry("HelloWorld")) {
                // 被保护的逻辑
                System.out.println("hello world");
            } catch (BlockException ex) {
                // 处理被流控的逻辑
                System.out.println("blocked!");
            }
        }
    }
    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("HelloWorld");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 20.
        rule.setCount(20);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

然后我们需要根据官方文档下载sentinel的控制台,就是下载它的jar包然后根据官方文档提供步骤在命令行进行启动,然后访问控制台。

启动的命令:java -Dserver.port=8131 -jar sentinel-dashboard-1.8.6.jar

我这边设置启动的端口是8131,本地输入http://localhost:8131/,即可访问控制台,账号密码默认都是sentinel

接着在我们的项目中引入控制台的依赖,使项目与sentinel控制台通讯:

复制代码
<!--        sentinel控制台-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.8.6</version>
        </dependency>

然后我们需要配置一下sentinel的JVM虚拟机参数才能在控制台展示使用,步骤如下:

然后输入参数:-Dcsp.sentinel.dashboard.server=localhost:8131

然后启动这个SentinelTest,刷新控制台就可以在控制台看到数据了。

规则管理和推送:sentinel的规则存储在哪里呢?又是如何通过控制台修改规则后,将规则同步给客户端进行限流熔断呢?

官方文档中说,规则推送主要分3种模式:原始模式、pull模式、push模式。

目前控制台的规则推送也是通过 规则查询更改 HTTP API 来更改规则。这也意味着这些规则默认仅在内存态 生效,应用重启之后,该规则会丢失。

以上是原始模式。当了解了原始模式之后,官方建议通过 动态规则 并结合各种外部存储来定制自己的规则源。我们推荐通过动态配置源的控制台来进行规则写入和推送,而不是通过 Sentinel 客户端直接写入到动态配置源中。

在生产环境中,官方推荐 push 模式,支持自定义存储规则的配置中心,控制台改变规则后,会 push 到配置中心。

那我们怎么在项目中使用呢?下面来整合到springboot实践它(基于springboot starter+注解模式开发+原始规则推送模式开发):

在引入整合依赖时,一定要注意版本号,参考官方的版本说明选择版本号。那我们的项目采用的springboot版本是2.7.2版本的,因此使用的sentinel starter版本为2021.0.5.0,在项目中引入依赖(可以把刚才引入的两个sentinel依赖都删掉了,因为sentinel starter它默认给我们整合了这两个依赖):

复制代码
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2021.0.5.0</version>
</dependency>

然后我们刚才是不是给SentinelTest添加了JVM参数配置,同样的我们现在要运用到整个项目,我们去给启动类MainApplication添加上刚才的那个参数配置。

然后启动项目,并且去接口文档随便发送个请求一下,我们的sentinel控制台就会接收到资源,自动注册对应请求的接口。

通过控制台自动识别是不是很方便,当然了,sentinel官方文档还有多种不同的方式定义资源和定义规则。

开发模式:

建议按照优先使用适配包来自动识别资源,然后能运用注解尽量运用注解,最后再选择主动编码定义资源。

回归到项目,将sentinel保护资源,限流、熔断等用到我们项目中的刚才那两条需求分析。

下面我们来在实际项目场景中运用sentinel进行流量控制和熔断

后端开发(sentinel实战)

1、查看题库列表接口限流熔断:

资源:listQuestionBankVOByPage 接囗

目的:控制对耗时较长的、经常访问的接口的请求频率,防止过多请求导致系统过载。

限流规则:

策略:整个接口每秒钟不超过 10 次请求

阻塞操作:提示"系统压力过大,请耐心等待

熔断规则:

熔断条件:如果接口异常率超过 10%,或者慢调用(响应时长>3秒)的比例大于 20%,触发 60 秒熔断。

熔断操作:直接返回本地数据(缓存或空数据)

开发模式:用注解定义资源 + 基于控制台定义规则

1)定义资源:给需要限流的接口添加@SentinelResource注解

复制代码
@PostMapping("/list/page/vo")
@SentinelResource(value = "listQuestionBankVOByPage",
                  blockHandler = "handleBlockException",
                  fallback = "handleFallback")
public BaseResponse<Page<QuestionBankVO>> listQuestionBankVOByPage(@RequestBody QuestionBankQueryRequest
questionBankQueryRequest,HttpServletRequest request) {

2)实现限流阻塞和熔断降级方法

然后我们需要来定义这些处理的方法blockHandler和fallback(为了方便快速验证实现,直接在controller里面编写):

复制代码
/**
 * listQuestionBankVOByPage 降级操作:直接返回本地数据
 */
public BaseResponse<Page<QuestionBankVO>> handleFallback(@RequestBody QuestionBankQueryRequest questionBankQueryRequest,
                                                         HttpServletRequest request, Throwable ex) {
    // 可以返回本地数据或空数据
    return ResultUtils.success(null);
}

/**
 * listQuestionBankVOByPage 流控操作
 * 限流:提示"系统压力过大,请耐心等待"
 */
public BaseResponse<Page<QuestionBankVO>> handleBlockException(@RequestBody QuestionBankQueryRequest questionBankQueryRequest,
                                                               HttpServletRequest request, BlockException ex) {
    //降级操作
        if(ex instanceof DegradeException){
            return handleFallback(questionBankQueryRequest, request, ex);
        }
    // 限流操作
    return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统压力过大,请耐心等待");
}

3)定义规则:接着我们重启项目,打开接口文档随便发一个对应接口的请求,然后打开sentinel的控制台的簇点链路会有一个listQuestionBankVOByPage,然后我们可以点击流控给它配置一个限流规则,设置单机阈值为10。熔断的熔断规则,先设置慢调用比例为:最大RT3000,比例阈值0.2,熔断时长60,最小请求数10,统计时长30000;然后设置异常比例为:比例阈值0.1,熔断时长60,最小请求数10,统计时长30000。

2、单IP查看题目列表限流熔断:

资源:listQuestionVoByPage 接口

限流规则:

策略:每个IP 地址每分钟允许查看题目列表的次数不能超过 60 次。

阻塞操作:提示"访问过于频繁,请稍后再试

熔断规则:

熔断条件:如果接口异常率超过 10%,或者慢调用(响应时长>3秒)的比例大于 20%,触发 60 秒熔断。

熔断操作:直接返回本地数据(缓存或空数据)

开发模式:编程式定义资源 + 编码方式定义规则

需要针对每一个用户进一步细化限流,而不是整体接口限流,可以采用热点参数限流机制,允许根据参数控制限流触发条件,

因此我们可以将IP地址作为热点参数。

1)定义资源:

首先我们要在QuestionController中重新写一个listQuestionVOByPageSentinel接口,不要在listQuestionVOByPage里写,等测试稳定后再进行切换,代码如下:

复制代码
/**
 * 分页获取题目列表(封装类)---限流版
 *
 * @param questionQueryRequest
 * @param request
 * @return
 */
@PostMapping("/list/page/vo/sentinel")
public BaseResponse<Page<QuestionVO>> listQuestionVOByPageSentinel(@RequestBody QuestionQueryRequest questionQueryRequest,
                                                           HttpServletRequest request) {
    ThrowUtils.throwIf(questionQueryRequest == null, ErrorCode.PARAMS_ERROR);
    long size = questionQueryRequest.getPageSize();
    // 限制爬虫
    ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
    // 基于 IP 限流
    String remoteAddr = request.getRemoteAddr();
    Entry entry = null;
    try  {
        entry = SphU.entry("listQuestionVOByPage", EntryType.IN, 1, remoteAddr);
        // 被保护的业务逻辑
        // 查询数据库
        Page<Question> questionPage = questionService.listQuestionByPage(questionQueryRequest);
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVOPage(questionPage, request));
    } catch (BlockException ex) {
        // 资源访问阻止,被限流或被降级
        if (ex instanceof DegradeException) {
            return handleFallback(questionQueryRequest, request, ex);
        }
        // 限流操作
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "访问过于频繁,请稍后再试");
    } finally {
        if (entry != null) {
            entry.exit(1, remoteAddr);
        }
    }
}
/**
 * listQuestionVOByPageSentinel 降级操作:直接返回本地数据
 */
public BaseResponse<Page<QuestionVO>> handleFallback(@RequestBody QuestionQueryRequest questionQueryRequestcccccccccc,
                                                         HttpServletRequest request, Throwable c) {
    // 可以返回本地数据或空数据
    return ResultUtils.success(null);
}

注意 Sentinel 的降级仅针对业务异常,对 Sentinel限流降级本身的异常 BlockException 不生效。为了统计异常比例或异常数,需要手动通过 Tracer.trace(ex)记录业务异常。

为什么上一个需求中,我们不用手动调用 Tracer 上报异常呢?因为使用 Sentinel 的开源整合模块,如 SentineDubbo Adapter, Sentinel Web Servlet filter 或 @sentinelResource 注解会自动统计业务异常,无需手动调用。这里需要给我们的资源定义增加异常统计代码,修改的部分修改后代码如下:

复制代码
 catch (Throwable ex) {
    // 业务异常
    if (!BlockException.isBlockException(ex)) {
        Tracer.trace(ex);
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
    }
    // 降级操作
    if (ex instanceof DegradeException) {
        return handleFallback(questionQueryRequest, request, ex);
    }
    // 限流操作
    return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "访问过于频繁,请稍后再试");

2)编写阻塞和降级操作代码:

这个其实就是刚才写的handleFallback方法,刚刚已经写完了。

3)定义规则(上个需求是使用控制台定义规则,每次都要用控制台来有点麻烦,这边使用编码形式):

新建一个sentinel包并创建一个SentinelRulesManager作为Bean,利用@PostConstruct注解,在Bean加载后创建规则,代码如下:

复制代码
@Component
public class SentinelRulesManager {
    @PostConstruct
    public void initRules() {
        initFlowRules();
        initDegradeRules();
    }

    // 限流规则
    public void initFlowRules() {
        // 单 IP 查看题目列表限流规则
        ParamFlowRule rule = new ParamFlowRule("listQuestionVOByPage")
                .setParamIdx(0) // 对第 0 个参数限流,即 IP 地址
                .setCount(60) // 每分钟最多 60 次
                .setDurationInSec(60); // 规则的统计周期为 60 秒
        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
    }

    // 降级规则
    public void initDegradeRules() {
        // 单 IP 查看题目列表熔断规则
        DegradeRule slowCallRule = new DegradeRule("listQuestionVOByPage")
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                .setCount(0.2) // 慢调用比例大于 20%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(30 * 1000) // 统计时长 30 秒
                .setMinRequestAmount(10) // 最小请求数
                .setSlowRatioThreshold(3); // 响应时间超过 3 秒

        DegradeRule errorRateRule = new DegradeRule("listQuestionVOByPage")
                .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
                .setCount(0.1) // 异常率大于 10%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(30 * 1000) // 统计时长 30 秒
                .setMinRequestAmount(10); // 最小请求数

        // 加载规则
        DegradeRuleManager.loadRules(Arrays.asList(slowCallRule, errorRateRule));
    }
}

然后我们启动项目,打开sentinel控制台就能看到规则了,然后进行测试。

**注意:**如果你关机了,每次启动项目前都要先通过命令启动sentinel(要在所属jar包的文件中输入cmd然后输入启动命令java -Dserver.port=8131 -jar sentinel-dashboard-1.8.6.jar)

扩展

1、将规则配置本地持久化(用文件来本地持久化配置,这样项目重启就不会丢失)

在SentinelRulesManager中编写代码使用:

复制代码
     /**
     * 持久化配置为本地文件
     */
    public void listenRules() throws Exception {
        // 获取项目根目录
        String rootPath = System.getProperty("user.dir");
        // sentinel 目录路径
        File sentinelDir = new File(rootPath, "sentinel");
        // 目录不存在则创建
        if (!FileUtil.exist(sentinelDir)) {
            FileUtil.mkdir(sentinelDir);
        }
        // 规则文件路径
        String flowRulePath = new File(sentinelDir, "FlowRule.json").getAbsolutePath();
        String degradeRulePath = new File(sentinelDir, "DegradeRule.json").getAbsolutePath();

        // Data source for FlowRule
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(flowRulePath, flowRuleListParser);
        // Register to flow rule manager.
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
        WritableDataSource<List<FlowRule>> flowWds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
        // Register to writable data source registry so that rules can be updated to file
        WritableDataSourceRegistry.registerFlowDataSource(flowWds);

        // Data source for DegradeRule
        FileRefreshableDataSource<List<DegradeRule>> degradeRuleDataSource
                = new FileRefreshableDataSource<>(
                degradeRulePath, degradeRuleListParser);
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
        WritableDataSource<List<DegradeRule>> degradeWds = new FileWritableDataSource<>(degradeRulePath, this::encodeJson);
        // Register to writable data source registry so that rules can be updated to file
        WritableDataSourceRegistry.registerDegradeDataSource(degradeWds);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<FlowRule>>() {
            });
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<DegradeRule>>() {
            });

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }

当然了,也可以根据官方文档使用push模式,用Nacos来做持久化。

2、封装限流组件为Spring Boot Starter(可做可不做吧)

相关推荐
木辰風7 小时前
PLSQL自定义自动替换(AutoReplace)
java·数据库·sql
heartbeat..7 小时前
Redis 中的锁:核心实现、类型与最佳实践
java·数据库·redis·缓存·并发
7 小时前
java关于内部类
java·开发语言
好好沉淀7 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin7 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder7 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
吨~吨~吨~7 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea
你才是臭弟弟7 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
短剑重铸之日7 小时前
《设计模式》第二篇:单例模式
java·单例模式·设计模式·懒汉式·恶汉式
码农水水7 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展