当前位置: 首页 > news >正文

流量安全——基于Sentinel实现限流,熔断,降级

避免系统崩溃,保护服务稳定性常见的防护措施

流量控制

流量控制是为了 防止系统被过多的请求压垮,确保资源合理分配并保持服务的可用性,比如对请求数量的限制。

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

  1. 防止过载:当瞬间涌入的请求量超出系统处理能力时,会导致资源枯竭,如 CPU 和内存耗尽。流量控制通过限制系统能处理的请求数,确保不会发生过载。

  2. 避免雪崩效应:高负载下某个服务崩溃可能引发其他依赖服务的崩溃,形成连锁反应。流量控制可以有效预防这种连锁故障,避免系统雪崩。

  3. 优化用户体验:即便部分请求被拒绝或延迟处理,流量控制也能确保大部分用户的请求能够正常响应,避免全局响应时间过长的情况。

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

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

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

熔断机制

熔断机制的目的是 避免当下游服务发生异常时,整个系统继续耗费资源重复发起失败请求,从而防止连锁故障。

类似于电路中的断路器,当检测到异常情况时,熔断器会自动切断对故障服务的调用,防止问题扩大。

工作机制:

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

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

  3. 快速失败:在熔断状态下,系统直接返回失败响应(也可以降级处理)

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

熔断流程:

降级机制

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

降级机制的好处:

  1. 优雅地处理故障:在降级状态下,系统不会直接返回错误信息,而是提供一个替代方案。例如,某个数据查询服务不可用时,系统可以返回缓存数据,确保用户看到的是有效信息,而非错误页面。

  2. 降低服务压力:降级有助于减轻系统对非核心服务的依赖,确保核心功能的稳定运行。例如,当推荐系统或广告服务出现故障时,降级可以减少对这些服务的调用,保护系统的整体稳定性。

熔断和降级的区别

熔断不一定要降级,只是切断调用;降级也不一定需要熔断,单次调用失败也可以降级(比如数据库查询失败返回内存的数据)。

具体来说:

  • 熔断是当服务健康状况恶化时,通过 切断调用 避免系统资源浪费或服务间故障扩散。

  • 降级是在系统压力过大或某个服务不可用时,通过 提供简化的替代方案 ,保持系统的可用性和用户体验。

两者经常结合使用,先触发熔断后再进行降级。

Sentinel

Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。

  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel 控制台是 Sentinel 的核心优势,是灵魂所在!!!

Sentinel 核心功能:

  • 流量控制: https://sentinelguard.io/zh-cn/docs/flow-control.html

  • 熔断降级: https://sentinelguard.io/zh-cn/docs/circuit-breaking.html

  • 系统负载保护: https://sentinelguard.io/zh-cn/docs/system-adaptive-protection.html

  • 消息削峰填谷:瞬时有大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高。但后面几秒都没有消息投递,

    Sentinel 的 Rate Limiter 模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利 用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性

规则管理和推送

问题:Sentinel 的规则存储在哪里呢?又是如何通过控制台修改规则之后,将规则同步给客户端进行限流熔断的呢?

官方文档 有详细地介绍:Sentinel 控制台同时提供简单的规则管理以及推送的功能。规则推送分为 3 种模式

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

了解了原始模式之后,官方建议通过 动态规则 并结合各种外部存储来定制自己的规则源。

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

下载并启动 Sentinel 控制台

1)下载控制台 jar 包并在本地启动

可以访问从 github 上下载 release的 jar 包。

2)直接在命令行窗口启动 Sentinel 控制台: 注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。

 java -Dserver.port=8131 -jar sentinel-dashboard-1.8.6.jar

注意: 端口的地址自己设置 sentinel-dashboard-1.8.6.jar为你jar包的实际名称

3)本地访问 http://localhost:8131/(你填的端口),即可访问控制台

默认账号和密码都是 sentinel

4)通过整合 Spring Boot 客户端接入控制台

引入 Maven 依赖,用于和 Sentinel 控制台通讯:(在引入整合依赖时,一定要注意版本号,一定要选择相适配的版本!)

建议 参考官方文档选择版本  或查看Spring 的官网

以Spring Boot 2.7为例,因此使用 Sentinel Starter 的版本 2021.0.5.0。在项目中引入依赖:

 <!-- 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>

该依赖自动整合了 Sentinel 的 core 包、客户端通讯包、注解开发包、webmvc 适配包、热点参数限流包等:

5)指定 Sentinel 客户端连接的 Sentinel 控制台

程序启动时需要加入 JVM 参数—— 指定 Sentinel 客户端连接的 Sentinel 控制台(Dashboard)的地址和端口

Sentinel 客户端参数说明

1,告诉客户端要连接的 Sentinel 控制台( Dashboard )的位置

在应用启动时,需要通过 JVM 参数指定 Sentinel 控制台地址和端口:

 -Dcsp.sentinel.dashboard.server=localhost:8131

2.指定客户端对外暴露的监控 API 端口。 Sentinel 客户端在本地会启动一个 HTTP Server(默认端口 8719),供控制台回连获取监控数据。可以通过以下参数指定:

 -Dcsp.sentinel.api.port=xxxx

单机仅有一个应用时,可以使用默认 8719;

如果同一台机器运行多个应用,则必须为每个应用配置不同的端口,避免冲突。

注意: Dcsp.sentinel.dashboard.server 是 Sentinel 客户端用来确定连接 Sentinel 控制台地址的系统属性名。 -D 是用来设置系统属性(System Property)的参数。

开发模式

Sentinel 的开发主要包括定义资源和定义规则。

定义资源

支持通过代码、引入框架适配、注解方式 定义资源。

通过代码定义资源
 Entry entry = null;// 务必保证finally会被执行try {// 资源名可使用任意有业务语义的字符串entry = SphU.entry("自定义资源名");// 被保护的业务逻辑// do something...} catch (BlockException e1) {// 资源访问阻止,被限流或被降级// 进行相应的处理操作} finally {if (entry != null) {entry.exit();}}
通过注解定义资源——更快捷可读

@SentinelResource 注解并不会定义规则

 @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")

注解的作用是告诉 Sentinel:

  • 当前方法是一个「资源」;

  • 当命中规则时,用我指定的 blockHandler / fallback 来处理。

 public class TestService {​// 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})public void test() {System.out.println("Test");}​// 原函数@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")public String hello(long s) {return String.format("Hello at %d", s);}// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.public String helloFallback(long s) {return String.format("Halooooo %d", s);}​// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.public String exceptionHandler(long s, BlockException ex) {// Do some log here.ex.printStackTrace();return "Oops, error occurred at " + s;}}
通过框架适配自动识别

有 HTTP 路径(Controller 方法)

当方法上有 @GetMapping / @PostMapping 等注解时,这个方法会成为 HTTP 请求入口。

如果项目里引入了 spring-cloud-starter-alibaba-sentinel(包含 sentinel-web-servlet 适配包):

Sentinel 会自动把 请求路径 识别为资源。

在 Sentinel 控制台中会看到资源名:/order/{id}

这种情况 无需额外加 @SentinelResource 注解。

如果 控制台上定义的规则命中了对应的 URL 资源由 全局的 BlockExceptionHandler 决定返回什么

推荐:优先使用框架的适配包来自动识别资源,然后能运用注解尽量运用注解。

同时使用注解和框架的自动识别
 @PostMapping("/list/page/vo")@SentinelResource(value = "listQuestionBankVOByPage",    //指定资源名为 listQuestionBankVOByPageblockHandler = "handleBlockException",           //指定限流/熔断等 Sentinel 规则被触发(抛出 BlockException)时的兜底方法名。该方法与原方法返回类型相同、参数列表一致,并额外多一个 BlockException 参数。          //指定业务异常(原方法内抛出的运行时异常等)时的兜底方法名。fallback = "handleFallback")public BaseResponse<Page<QuestionBankVO>> listQuestionBankVOByPage(@RequestBody QuestionBankQueryRequest questionBankQueryRequest,HttpServletRequest request) {......................................}

引入了 spring-cloud-starter-alibaba-sentinel(含 sentinel-web-servlet 适配),此 @PostMapping("/list/page/vo") 的 HTTP 接口会被自动识别为资源并受规则保护。因为仅做入口限流/熔断时,可以不用再加 @SentinelResource

但在以下需求下应保留 @SentinelResource

  1. 方法兜底逻辑

  • 需要在当前方法内定义限流/熔断/异常的兜底处理时,必须用注解。

  • 不加注解时,触发规则会走全局的 BlockExceptionHandler,而不是你的 handleBlockException

两层都在控制台配了规则:URL 是外层,优先评估;若 URL 已命中并拦截,在控制台定义的对应的URL规则如果命中了,进行限流,那么即使加了该注解,也不会抛出注解中的方法

定义规则:支持通过代码、控制台

比如通过代码定义一个限流规则

private static void initFlowQpsRule() {List<FlowRule> rules = new ArrayList<>();FlowRule rule1 = new FlowRule();rule1.setResource(resource);// Set max qps to 20rule1.setCount(20);rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);rule1.setLimitApp("default");rules.add(rule1);FlowRuleManager.loadRules(rules);
}

通过控制台配置(规则推送到客户端内存中,不会持久化保存):

  • 控制台规则 → 决定是否触发限流/降级。

规则配置本地持久化

参考官方文档的配置:https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html

用文件来本地持久化配置

这样重启项目后配置就不会丢失了。

  • 读写本地文件 Demo(先看这个)

  • 读本地文件 Demo

可以在 SentinelManager 的初始化逻辑中调用该样板代码:

/*** 持久化配置为本地文件*/
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 FlowRuleReadableDataSource<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 fileWritableDataSourceRegistry.registerFlowDataSource(flowWds);// Data source for DegradeRuleFileRefreshableDataSource<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 fileWritableDataSourceRegistry.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 规则推送模式,定义自己的持久化规则

参考文档:

  • https://sentinelguard.io/zh-cn/docs/basic-api-resource-rule.html(结尾提到)

  • https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html

后端开发(Sentinel 实战)

对查看题库列表接口限流熔断

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

思路:

限流规则:

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

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

熔断规则:

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

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

步骤

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

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

@PostMapping("/list/page/vo")
@SentinelResource(value = "listQuestionBankVOByPage",    //指定资源名为 listQuestionBankVOByPageblockHandler = "handleBlockException",           //指定限流/熔断等 Sentinel 规则被触发(抛出 BlockException)时的兜底方法名。该方法与原方法返回类型相同、参数列表一致,并额外多一个 BlockException 参数。          //指定业务异常(原方法内抛出的运行时异常等)时的兜底方法名。fallback = "handleFallback")
public BaseResponse<Page<QuestionBankVO>> listQuestionBankVOByPage(@RequestBody QuestionBankQueryRequest questionBankQueryRequest,HttpServletRequest request) {......................................
}

上述代码中,参考 注解使用官方文档 指定了资源名称、阻塞处理器和降级处理器。

blockHandler——触发条件:Sentinel 的 规则被触发(如限流、熔断、系统保护,抛出 BlockException`)。

fallback——触发条件:方法内部抛出了 业务异常/运行时异常(例如 NullPointerException、数据库异常)。

2)启动项目,注意需加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。

启动项目成功并且访问接口后,可以在控制台看到刚定义的资源:

3)实现限流阻塞和熔断降级方法。注意遵循 官方文档的方法定义规则:

如果 blockHandlerfallbackHandler 同时配置,当熔断器打开后,仍然会进入 blockHandler 进行处理,因此需要在handleBlockException该方法中处理因为熔断触发的降级逻辑:

/*** 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, "系统压力过大,请耐心等待");
}

4)通过控制台定义规则

配置熔断和限流的规则

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

思路

限流规则:

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

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

熔断规则:

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

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

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

步骤

1)定义资源

使用编程式定义资源的方法。

// 基于 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 (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, "访问过于频繁,请稍后再试");
} finally {if (entry != null) {entry.exit(1, remoteAddr);}
}/*** listQuestionVOByPage 降级操作:直接返回本地数据*/
public BaseResponse<Page<QuestionVO>> handleFallback(QuestionQueryRequest questionQueryRequest,HttpServletRequest request, Throwable ex) {// 可以返回本地数据或空数据return ResultUtils.success(null);
}

要保护的资源放在try中 如果被限流了会抛异常 ,没被限流执行正常的业务逻辑

💡需要特别注意!

  1. 若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。

  2. SphU.entry(xxx) 上报资源和 需要与 entry.exit()释放资源 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。

  3. 如果手动处理异常,不是用注解的方式,一定要手动上报异常到Sentinel Tracer.trace(ex) Sentinel 才会得到你的异常并进行熔断

使用 Sentinel 的开源整合模块,如 Sentinel Dubbo Adapter, Sentinel Web Servlet Filter 或 @SentinelResource 注解会自动统计业务异常,无需手动调用。

2)通过编码方式定义规则(实现长久保存)

可以新建 sentinel 包并定义一个单独的 Manager 作为 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));}
}

4)测试

启动项目就能看到规则

http://www.dtcms.com/a/434848.html

相关文章:

  • Semaphore GUI 详细介绍
  • 中山网站优化营销做专业课视频课的网站
  • 元表纪基于一个Excel表实现一键发货、打印面单
  • 企业外贸网站建设建设一个直播网站多少钱
  • 网站建设需要基础吗电子商务网站建设与管理是什么
  • 【LeetCode - 每日1题】换水问题1
  • 资深面试之MySQL 问题及解答(一)
  • 自定义脚手架
  • 云空间布置网站seo顾问赚钱吗
  • 网络设备中的硬件转发和软件转发
  • 永州建设网站公司网站开发费的税率是多少
  • js时间格式转化器
  • 攻防世界-Web-Web_php_unserialize
  • Deep Learning|01 RBF Network
  • 指针步长:C/C++内存操控的核心法则
  • 服装网站建设分析wordpress模板如何用
  • wordpress后台菜单管理程序代码优化网站
  • Windows 常用短文件名(8.3 格式)介绍
  • 【stm32】【edgetx】解析链接脚本文件(ld)
  • 商务网站构建与维护网站建设所有权
  • C语言速成秘籍——跳转语句(goto)
  • WPF实现串口热插拔 (提供百度网盘源代码)
  • 企业网站关键词排名南京比较好的网络策划公司
  • FFmpeg 核心 API 系列:avcodec_find_decoder / avcodec_alloc_context3 / avcodec_open2
  • 文件上传简单的绕过总结
  • Visual Studio Code中launch.json深度解析:C++调试的艺术
  • 天长市建设局网站惠来做网站
  • 51单片机红外遥控
  • Java 集合 “List + Set”面试清单(含超通俗生活案例与深度理解)
  • 云南网站建设哪个好软文广告平台