一、一次"雪崩"让我彻底清醒
凌晨三点,刺耳的报警声把我从睡梦中惊醒。
监控系统显示:订单服务RT飙升,99线延迟超过5秒。紧接着,支付服务、库存服务、物流服务......一个接一个地沦陷,整个系统陷入了瘫痪。
我赶紧爬起来排查。发现起因很简单:某个上游服务突然变慢,导致调用它的服务线程池被占满,然后拖垮了更多服务,最终引发了连锁反应------这就是传说中的"雪崩效应"。
那个夜晚,我一边手动重启服务,一边发誓:再也不让这种事发生。
后来,我接触到了Sentinel。这个阿里开源的流量控制组件,彻底改变了我的系统防护思路。今天就把我的经验分享出来,希望你别再踩我踩过的坑。
二、熔断与限流:傻傻分不清?
很多人把熔断和限流混为一谈,其实它们解决的问题完全不同。
2.1 限流:保护系统不被"挤爆"
场景:每秒1万个请求,但服务只能处理1000个
问题:如果不限制,全部1万个请求都会打到服务上,导致服务崩溃
限流方案:只放行1000个请求,其余9000个拒绝或排队
限流的核心是保护"系统本身",让服务不被瞬时流量打垮。
2.2 熔断:保护系统不被"慢服务"拖垮
场景:服务A依赖服务B,服务B突然变慢
问题:如果持续调用变慢的服务B,服务A的线程池会被耗尽,最终服务A也变慢
熔断方案:检测到服务B持续变慢时,"跳闸"------暂时不调用服务B,直接返回降级结果
熔断的核心是"隔离故障",防止故障蔓延。
2.3 降级:给用户一个"Plan B"
java
// 没有降级
public OrderResult createOrder(OrderDTO order) {
// 调用库存服务
InventoryResponse inventory = inventoryService.check(order.getSkuId());
// ...
}
// 有降级
public OrderResult createOrder(OrderDTO order) {
try {
InventoryResponse inventory = inventoryService.check(order.getSkuId());
} catch (Exception e) {
// 降级:允许无库存下单,后续处理
log.warn("库存服务不可用,使用降级策略");
InventoryResponse fallback = new InventoryResponse();
fallback.setAvailable(false);
fallback.setMessage("库存服务降级,稍后确认");
// 继续处理订单
}
}
降级的核心是"有备无患",当服务不可用时,给出一个兜底方案。
三、Sentinel核心概念
3.1 三大核心组件
Sentinel核心架构:
┌─────────────────────────────────────────────────┐
│ Sentinel Core │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ 限流规则 │ │ 熔断规则 │ │ 热点规则 │ │
│ │ (FlowRule) │ │(DegradeRule)│ │(ParamFlow)│ │
│ └─────────────┘ └─────────────┘ └──────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Slot Chain(插槽链) │ │
│ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │Node│→│Flow│→│Degr│→│Sys │→│Auth│ │ │
│ │ │统计│ │限流│ │熔断│ │系统│ │授权│ │ │
│ │ └────┘ └────┘ └────┘ └────┘ └────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
3.2 限流算法:四种模式
java
// 1. 直接拒绝模式(直接拒绝超出阈值的请求)
FlowRule rule = new FlowRule();
rule.setResource("orderService");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 按QPS限流
rule.setCount(100); // 每秒最多100个请求
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
sentinelRuleManager.save(rule);
// 2. 冷启动模式(渐进式增加流量,防止冷启动冲击)
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
// 冷启动持续时间:10秒,从0逐步增加到100
// 3. 匀速排队模式(让请求匀速通过,防止突刺)
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
rule.setMaxQueueingTimeMs(500); // 最大排队时间500ms
// 4. 冷启动+匀速排队
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER);
3.3 熔断策略:三种模式
java
// 1. 慢调用比例熔断
DegradeRule rule = new DegradeRule();
rule.setResource("orderService");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 按响应时间
rule.setCount(2000); // 超过2000ms视为慢调用
rule.setSlowRatioThreshold(0.5); // 慢调用比例阈值50%
rule.setMinRequestAmount(5); // 最小请求数5个
rule.setStatIntervalMs(10000); // 10秒内
// 触发条件:10秒内至少5个请求,且50%以上超过2秒
// 熔断时长:10秒
// 2. 异常比例熔断
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
rule.setCount(0.5); // 异常比例50%
rule.setMinRequestAmount(5);
// 触发条件:5个请求中,50%以上异常
// 3. 异常数熔断
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setCount(10); // 异常数达到10个
rule.setMinRequestAmount(5);
四、Sentinel实战:Spring Boot集成
4.1 引入依赖
xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version>
</dependency>
<!-- Spring Boot适配 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-boot-starter</artifactId>
<version>1.8.6</version>
</dependency>
<!-- 注解支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.6</version>
</dependency>
4.2 配置Sentinel
yaml
# application.yml
spring:
cloud:
sentinel:
enabled: true
transport:
dashboard: localhost:8080 # Sentinel控制台地址
port: 8719 # 客户端端口
eager: true # 启动时加载规则
4.3 使用Sentinel注解
java
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 使用Sentinel资源保护
* resource: 资源名(唯一标识)
* blockHandler: 限流/熔断时的处理方法
* fallback: 异常时的降级方法
*/
@GetMapping("/create")
@SentinelResource(value = "orderCreate",
blockHandler = "orderBlockHandler",
fallback = "orderFallback")
public Result<OrderDTO> createOrder(@RequestParam String userId,
@RequestParam String skuId) {
return orderService.createOrder(userId, skuId);
}
/**
* 限流/熔断时的处理
*/
public Result<OrderDTO> orderBlockHandler(String userId, String skuId,
BlockException e) {
log.warn("订单服务被限流/熔断: {}", e.getClass().getSimpleName());
return Result.fail("系统繁忙,请稍后再试");
}
/**
* 异常降级
*/
public Result<OrderDTO> orderFallback(String userId, String skuId,
Throwable e) {
log.error("订单服务异常: userId={}", userId, e);
return Result.fail("服务异常,请稍后再试");
}
}
五、限流规则实战配置
5.1 QPS限流
java
// 按QPS限流,每秒最多100个请求
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("orderCreate");
rule.setCount(100); // QPS阈值
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 按QPS
rule.setLimitApp("default"); // 默认为所有应用
// 直接拒绝模式
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
5.2 并发线程数限流
java
// 按并发线程数限流
private static void initThreadFlowRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("orderCreate");
rule.setCount(10); // 最多10个并发线程
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); // 按线程数
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
5.3 基于调用关系的限流
java
// 调用关系限流:从属资源限流
// 当 orderService 调用 inventoryService 时,对 orderService→inventoryService 这条链路限流
private static void initRelationFlowRule() {
List<FlowRule> rules = new ArrayList<>();
// 入口流量限流
FlowRule entryRule = new FlowRule();
entryRule.setResource("orderCreate");
entryRule.setCount(100);
entryRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
entryRule.setStrategy(RuleConstant.STRATEGY_DIRECT); // 直接限流
rules.add(entryRule);
// 基于链路的限流
FlowRule chainRule = new FlowRule();
chainRule.setResource("inventoryService");
chainRule.setCount(50);
chainRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
chainRule.setStrategy(RuleConstant.STRATEGY_CHAIN); // 链路限流
chainRule.setLimitApp("orderService"); // orderService调用inventoryService时生效
rules.add(chainRule);
FlowRuleManager.loadRules(rules);
}
六、熔断规则实战配置
6.1 慢调用比例熔断
java
// 配置慢调用比例熔断
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("orderService");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 按RT熔断
rule.setCount(1000); // RT阈值1000ms
rule.setSlowRatioThreshold(0.5); // 50%慢调用比例触发熔断
rule.setMinRequestAmount(5); // 最小请求数
rule.setTimeWindow(10); // 熔断时长10秒
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
6.2 异常比例熔断
java
// 配置异常比例熔断
private static void initExceptionDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("orderService");
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
rule.setCount(0.5); // 50%异常比例
rule.setMinRequestAmount(5); // 最小请求数
rule.setTimeWindow(10); // 熔断时长10秒
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
6.3 熔断状态机
熔断器状态转换:
┌──────────┐
│ CLOSED │ 正常状态
│ (关闭) │ 所有请求正常通过
└────┬─────┘
│
│ 触发熔断条件
│ (慢调用≥50% 或 异常≥50%)
↓
┌──────────┐
│ OPEN │ 熔断状态
│ (打开) │ 所有请求被拒绝/降级
└────┬─────┘
│
│ 熔断时间到达
│ (等待10秒)
↓
┌──────────┐
│ HALF_OPEN│ 半开状态
│ (半开) │ 允许一个请求通过
└────┬─────┘
│
┌────────┴────────┐
↓ ↓
请求成功 请求失败
↓ ↓
┌────────┐ ┌────────┐
│CLOSED │ │ OPEN │
│恢复 │ │继续熔断│
└────────┘ └────────┘
七、热点规则:参数限流
7.1 热点参数限流
java
// 热点规则:对特定参数值限流
private static void initParamFlowRule() {
List<ParamFlowRule> rules = new ArrayList<>();
ParamFlowRule rule = new ParamFlowRule("orderQuery")
// 参数索引:0表示第一个参数(userId)
.setParamIdx(0)
.setCount(10); // 同一个userId,每秒最多10次
// 针对特定参数值的限流
ParamFlowItem item = new ParamFlowItem()
.setObject(String.valueOf("vip_user_001")) // VIP用户
.setClassType(String.class.getName())
.setCount(100); // VIP用户放宽到每秒100次
rule.setParamFlowItemList(Collections.singletonList(item));
rules.add(rule);
ParamFlowRuleManager.loadRules(rules);
}
7.2 代码中使用热点限流
java
@RestController
@RequestMapping("/product")
public class ProductController {
@GetMapping("/detail")
@SentinelResource(value = "productDetail",
blockHandler = "productBlockHandler")
public Result<Product> getProductDetail(
@RequestParam String productId,
@RequestParam(required = false) String userId) {
return productService.getProductDetail(productId);
}
public Result<Product> productBlockHandler(String productId, String userId,
BlockException e) {
return Result.fail("访问太频繁,请稍后再试");
}
}
八、Sentinel Dashboard配置
8.1 启动Dashboard
bash
# 下载 Sentinel Dashboard
wget https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar
# 启动(用户名/密码都是 sentinel)
java -Dserver.port=8080 \
-Dcsp.sentinel.dashboard.server=localhost:8080 \
-Dproject.name=sentinel-dashboard \
-jar sentinel-dashboard-1.8.6.jar
8.2 Dashboard配置限流规则
java
// 代码中定义资源
@SentinelResource(value = "orderCreate")
// Dashboard中配置规则(不需要重启应用)
// 1. 打开 http://localhost:8080
// 2. 登录(sentinel/sentinel)
// 3. 点击"流控规则" → "新增流控规则"
// 4. 填写:
// - 资源名:orderCreate
// - 阈值类型:QPS
// - 单机阈值:100
// - 流控模式:直接
// - 流控效果:快速失败
// 5. 点击"新增"
九、实战案例:订单系统限流保护
9.1 场景分析
订单系统架构:
用户请求
↓
┌─────────────────────────────────────┐
│ 限流层(Gateway) │
│ - 每秒最多10000请求 │
│ - 超出返回429 Too Many Requests │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 订单服务 │
│ - 每用户限流:每秒10次 │
│ - 每接口限流:每秒1000次 │
│ - 熔断:支付服务响应>2秒时触发 │
└─────────────────────────────────────┘
↓
┌───────────┬───────────┬───────────┐
│ 库存服务 │ 支付服务 │ 用户服务 │
│ (本地) │ (RPC) │ (RPC) │
└───────────┴───────────┴───────────┘
9.2 完整配置
java
@Configuration
public class SentinelConfig {
@PostConstruct
public void init() {
// 初始化限流规则
initFlowRules();
// 初始化熔断规则
initDegradeRules();
}
/**
* 限流规则初始化
*/
private void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
// 1. 订单创建接口限流:每秒100次
FlowRule createRule = new FlowRule();
createRule.setResource("orderCreate");
createRule.setCount(100);
createRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
createRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rules.add(createRule);
// 2. 订单查询接口限流:每秒500次
FlowRule queryRule = new FlowRule();
queryRule.setResource("orderQuery");
queryRule.setCount(500);
queryRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rules.add(queryRule);
// 3. 热点规则:热门商品查询每秒10次
ParamFlowRule hotRule = new ParamFlowRule("productDetail")
.setParamIdx(0) // productId参数
.setCount(10)
.setDurationInSec(1);
rules.add(hotRule);
FlowRuleManager.loadRules(rules);
}
/**
* 熔断规则初始化
*/
private void initDegradeRules() {
List<DegradeRule> rules = new ArrayList<>();
// 1. 支付服务熔断:RT>2秒且比例>50%时触发
DegradeRule paymentRule = new DegradeRule();
paymentRule.setResource("paymentService");
paymentRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
paymentRule.setCount(2000); // 2秒
paymentRule.setSlowRatioThreshold(0.5);
paymentRule.setMinRequestAmount(5);
paymentRule.setTimeWindow(10); // 熔断10秒
rules.add(paymentRule);
// 2. 库存服务熔断:异常比例>30%时触发
DegradeRule inventoryRule = new DegradeRule();
inventoryRule.setResource("inventoryService");
inventoryRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
inventoryRule.setCount(0.3); // 30%
inventoryRule.setMinRequestAmount(5);
inventoryRule.setTimeWindow(30);
rules.add(inventoryRule);
DegradeRuleManager.loadRules(rules);
}
}
十、踩坑实录
坑1:Sentinel规则没有持久化
项目上线后,在Dashboard上配置了限流规则。重启服务器后,规则全部丢失!
原因:Sentinel默认把规则存在内存中,重启后会丢失。
解决:使用动态规则推送,把规则存到Nacos、Apollo等配置中心,或者用Sentinel的Redis适配器。
java
// 使用Nacos动态推送规则
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.6</version>
</dependency>
// 配置Nacos数据源
spring:
cloud:
sentinel:
datasource:
ds:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-flow-rules
group-id: SENTINEL_GROUP
rule-type: flow
坑2:Sentinel注解在私有方法上不生效
我把
@SentinelResource加到了私有方法上,结果限流不生效。原因 :
@SentinelResource是基于AOP的,Spring AOP不会拦截同类内部调用。解决 :把方法移到另一个Service类中,或者使用
SphU.entry()手动埋点。
java
// 错误:内部调用,注解不生效
@Service
public class OrderService {
public void create() {
this.query(); // 内部调用,@SentinelResource不生效
}
@SentinelResource(value = "query", blockHandler = "block")
public void query() { }
}
// 正确:使用SphU手动埋点
@Service
public class OrderService {
public void create() {
try {
Entry entry = SphU.entry("query");
query();
entry.exit();
} catch (BlockException e) {
// 限流处理
}
}
}
坑3:熔断阈值设置过大
熔断阈值设置为5000ms(5秒),但实际上1秒以上的延迟就已经很危险了。
教训:熔断阈值要设置得比业务超时时间短,留出缓冲时间。建议设置为业务超时的50%-80%。
坑4:没有配置限流后的降级策略
限流后的请求直接报错,用户体验很差。
解决:配置友好的限流提示,或者跳转到排队页面。
java
// 自定义限流处理
public Result orderBlockHandler(String userId, String skuId, BlockException e) {
if (e instanceof FlowException) {
return Result.fail(429, "请求太频繁,请稍后再试");
}
if (e instanceof DegradeException) {
return Result.fail(503, "服务暂时不可用,请稍后再试");
}
return Result.fail("系统繁忙");
}
十一、总结
Sentinel是保护系统高可用的重要工具:
- 限流:保护系统不被瞬时流量打垮
- 熔断:防止故障蔓延,避免雪崩
- 降级:提供兜底方案,保证核心功能可用
- 热点参数限流:针对特定参数值的精细化限流
最佳实践:
- 限流阈值要合理,通过压测确定
- 熔断阈值要小于业务超时时间
- 限流/熔断后要给用户友好的提示
- 规则要持久化,不要只在内存中
- 监控要做好,能及时发现问题
血的教训:
不要以为上了限流和熔断就高枕无忧了。如果阈值设置不合理,该限的时候不限,该熔断的时候不熔断,问题照样会发生。建议在上线前做充分的压测和演练。
思考题: 你的系统目前有限流和熔断吗?阈值是怎么确定的?有没有遇到过"该限不限"的问题?
个人观点,仅供参考