避免系统崩溃,保护服务稳定性常见的防护措施
流量控制
流量控制是为了 防止系统被过多的请求压垮,确保资源合理分配并保持服务的可用性,比如对请求数量的限制。
流量控制的 3 个主要优势:
-
防止过载:当瞬间涌入的请求量超出系统处理能力时,会导致资源枯竭,如 CPU 和内存耗尽。流量控制通过限制系统能处理的请求数,确保不会发生过载。
-
避免雪崩效应:高负载下某个服务崩溃可能引发其他依赖服务的崩溃,形成连锁反应。流量控制可以有效预防这种连锁故障,避免系统雪崩。
-
优化用户体验:即便部分请求被拒绝或延迟处理,流量控制也能确保大部分用户的请求能够正常响应,避免全局响应时间过长的情况。
常见的实现流量控制方法有 2 种:
-
限流:通过固定窗口、令牌桶或漏桶等算法限制单位时间内的请求数量。
-
排队:当请求量超出处理能力时,部分请求进入等待队列,防止立即超载。
熔断机制
熔断机制的目的是 避免当下游服务发生异常时,整个系统继续耗费资源重复发起失败请求,从而防止连锁故障。
类似于电路中的断路器,当检测到异常情况时,熔断器会自动切断对故障服务的调用,防止问题扩大。
工作机制:
-
监控服务健康状态:系统会实时监控服务的调用情况,例如请求成功率、响应时间等,判断服务的健康状况。
-
进入熔断状态:当某个服务的错误率达到设定阈值时,系统会 激活熔断器,暂时停止对该服务的调用,避免消耗不必要的资源和让错误进一步扩散。
-
快速失败:在熔断状态下,系统直接返回失败响应(也可以降级处理)
-
熔断恢复机制:熔断并非永久状态。在一段时间后,熔断器会进入 半开状态,允许少量请求测试服务的健康情况。如果恢复正常,熔断器将关闭,恢复正常服务调用;如果仍有问题,则继续保持熔断。
熔断流程:

降级机制
降级的目的是在某个服务的响应能力下降、或该服务不可用时,提供简化版的功能或返回默认值作为 兜底,保持系统的部分功能可用,确保用户体验的连续性,避免系统频繁报错。
降级机制的好处:
-
优雅地处理故障:在降级状态下,系统不会直接返回错误信息,而是提供一个替代方案。例如,某个数据查询服务不可用时,系统可以返回缓存数据,确保用户看到的是有效信息,而非错误页面。
-
降低服务压力:降级有助于减轻系统对非核心服务的依赖,确保核心功能的稳定运行。例如,当推荐系统或广告服务出现故障时,降级可以减少对这些服务的调用,保护系统的整体稳定性。
熔断和降级的区别
熔断不一定要降级,只是切断调用;降级也不一定需要熔断,单次调用失败也可以降级(比如数据库查询失败返回内存的数据)。
具体来说:
-
熔断是当服务健康状况恶化时,通过 切断调用 避免系统资源浪费或服务间故障扩散。
-
降级是在系统压力过大或某个服务不可用时,通过 提供简化的替代方案 ,保持系统的可用性和用户体验。
两者经常结合使用,先触发熔断后再进行降级。
Sentinel
Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
Sentinel 分为两个部分:
-
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
-
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel 控制台是 Sentinel 的核心优势,是灵魂所在!!!
Sentinel 核心功能:
-
熔断降级: 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", //指定资源名为 listQuestionBankVOByPage
blockHandler = "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
:
- 方法兜底逻辑
-
需要在当前方法内定义限流/熔断/异常的兜底处理时,必须用注解。
-
不加注解时,触发规则会走全局的
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 20
rule1.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(先看这个)
可以在 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 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 规则推送模式,定义自己的持久化规则
参考文档:
-
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", //指定资源名为 listQuestionBankVOByPage
blockHandler = "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)实现限流阻塞和熔断降级方法。 注意遵循 官方文档的方法定义规则:
如果 blockHandler
和 fallbackHandler
同时配置,当熔断器打开后,仍然会进入 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中 如果被限流了会抛异常 ,没被限流执行正常的业务逻辑
💡需要特别注意!
-
若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(
exit(count, args)
),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。 -
SphU.entry(xxx)
上报资源和 需要与entry.exit()
释放资源 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出ErrorEntryFreeException
异常。 -
如果手动处理异常,不是用注解的方式,一定要手动上报异常到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)测试
启动项目就能看到规则