Sentinel 熔断限流原理深度解析
·
Sentinel 熔断限流原理深度解析
作者:薪火铺子(薪火铺子)
如果本文对你有帮助,欢迎关注薪火铺子,回复「深入理解SpringCloud与实战」获取全套学习笔记
核心要点
熔断限流是保障系统稳定性的重要手段:
- 需要理解限流算法(计数器、滑动窗口、令牌桶、漏桶)
- 需要掌握熔断器的工作机制(熔断打开、半开、关闭)
- 需要了解 Sentinel 的核心概念(资源、规则、插槽链)
本文将深入剖析 Sentinel 的实现原理,帮助你理解如何保护系统不被突发流量冲垮。
根因:没有限流保护!
**如果当时配置了 Sentinel,这场灾难完全可以避免。**
---
## 一、核心问题:为什么系统会被流量冲垮?
先看一个场景:
```mermaid
flowchart LR
subgraph 正常情况["正常情况(1000 QPS)"]
A1["用户"] --> A2["订单服务"] --> A3["数据库"]
A3 -->|"正常返回"| A2
A2 -->|"正常返回"| A1
end
subgraph 流量突增["流量突增(10000 QPS)"]
B1["用户 x10"] --> B2["订单服务"]
B2 -->|"堆积|排队|超时"| B3["数据库"]
B3 -->|"连接池耗尽"| B4["OOM"]
B4 --> B5["服务崩溃"]
end
style B4 fill:#ffcdd2
style B5 fill:#ffcdd2
问题:
- 为什么数据库会崩溃?
- 为什么一个服务崩溃会导致整个系统崩溃?
- 如何防止雪崩?
二、典型场景:雪崩是如何发生的
2.1 故障链路图
2.2 根因分析
2.3 解决方案
# 正确的 Sentinel 配置
spring:
cloud:
sentinel:
eager: true # 启动时加载规则
# 限流规则
sentinel:
flow:
order-service:
qps: 1000 # 每秒最多 1000 请求
grade: 1 # QPS 限流
# 熔断规则
sentinel:
degrade:
order-service:
grade: 1 # 慢调用比例
count: 2000 # 响应时间阈值 2000ms
ratio: 0.5 # 50% 慢调用触发熔断
time-window: 10 # 10 秒后尝试恢复
三、Sentinel 三大核心功能
| 功能 | 保护对象 | 触发条件 | 效果 |
|---|---|---|---|
| 流量控制 | 自身 | QPS/并发数超限 | 拒绝/排队/冷启动 |
| 熔断降级 | 下游 | 响应慢/异常多 | 快速失败 |
| 系统自适应 | 全局 | CPU/LOAD/线程数 | 全局限流 |
四、流量控制(Flow Control)
4.1 流量控制策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 直接拒绝 | 超过阈值直接拒绝 | 允许部分失败 |
| 冷启动 | 逐步增加阈值 | 秒杀、预热 |
| 排队等待 | 超过阈值排队 | 削峰填谷 |
4.2 滑动窗口算法原理
// 滑动窗口核心实现
public class SlidingWindow {
// 窗口数组(这里是 2 个子窗口,实际是 N 个)
private WindowWrap[] windowArray;
// 单个窗口的时间长度(毫秒)
private long windowLengthInMs;
// 采样窗口数量
private int sampleCount;
public SlidingWindow() {
this.windowLengthInMs = 1000 / SAMPLE_COUNT; // 500ms
this.windowArray = new WindowWrap[SAMPLE_COUNT];
}
public void record() {
// 1. 计算当前时间属于哪个窗口
long currentMs = System.currentTimeMillis();
int windowIdx = (int) (currentMs / windowLengthInMs % SAMPLE_COUNT);
// 2. 获取或创建窗口
WindowWrap window = getOrCreateWindow(currentMs);
// 3. 计数器 +1
window.counter.incrementAndGet();
}
public long getQPS() {
long total = 0;
for (WindowWrap window : windowArray) {
// 只统计最近一个时间窗口内的
if (isRecentWindow(window)) {
total += window.counter.get();
}
}
return total;
}
}
五、熔断降级(Degrade)
5.1 熔断器三种状态
5.2 三种熔断策略
| 策略 | 触发条件 | 说明 | 适用场景 |
|---|---|---|---|
| 慢调用比例 | 平均 RT > 阈值,且比例 > 设置值 | 响应太慢 | 下游服务性能下降 |
| 异常比例 | 异常比例 > 阈值 | 错误太多 | 下游服务不稳定 |
| 异常数 | 异常次数 > 阈值 | 错误次数 | 下游服务不可用 |
5.3 熔断器源码实现
// CircuitBreaker.java
public class CircuitBreaker {
private volatile State state = State.CLOSED;
public enum State {
CLOSED, // 关闭状态
OPEN, // 熔断状态
HALF_OPEN // 半开状态
}
// 熔断规则
private DegradeRule rule;
private long nextRetryTimestamp; // 下次重试时间
public void onRequest() {
switch (state) {
case CLOSED:
// 正常状态,放行请求
break;
case OPEN:
// 熔断状态,检查是否可以尝试恢复
if (System.currentTimeMillis() >= nextRetryTimestamp) {
// 进入半开状态
transitionToHalfOpen();
} else {
// 拒绝请求
throw new BlockException("Circuit breaker is OPEN");
}
break;
case HALF_OPEN:
// 半开状态,放行部分请求
break;
}
}
public void onSuccess() {
if (state == State.HALF_OPEN) {
// 连续成功,进入关闭状态
transitionToClosed();
}
}
public void onError(Throwable throwable) {
if (state == State.HALF_OPEN) {
// 半开状态失败,重新打开
transitionToOpen();
}
// 检查是否达到熔断条件
if (shouldOpen()) {
transitionToOpen();
}
}
private void transitionToOpen() {
// 进入熔断状态
this.state = State.OPEN;
// 计算下次重试时间 = 当前时间 + 熔断时长
this.nextRetryTimestamp = System.currentTimeMillis() + rule.getTimeWindow() * 1000;
RecordLog.info("Circuit breaker opened");
}
}
六、Sentinel 责任链模式
6.1 SlotChain 链路图
| Slot | 职责 |
|---|---|
| NodeSelectorSlot | 构建资源的调用链节点 |
| ClusterBuilderSlot | 构建集群节点统计信息 |
| LogSlot | 记录日志 |
| StatisticSlot | 统计实时数据(QPS、RT、异常数) |
| FlowSlot | 流量控制 |
| DegradeSlot | 熔断降级 |
| SystemSlot | 系统自适应保护 |
| AuthoritySlot | 黑白名单控制 |
6.2 请求处理流程
七、⚠️ 避坑指南:我踩过的 5 个坑
坑 1:限流阈值设置过大
# ❌ 错误:限流阈值太大,起不到保护作用
sentinel:
flow:
order-service:
qps: 10000 # 系统只能处理 5000,限流没用
# ✅ 正确:限流阈值 <= 系统处理能力
sentinel:
flow:
order-service:
qps: 5000 # 稍小于系统处理能力
control-behavior: queue # 排队等待
maxQueueingTimeMs: 5000 # 最多排队 5 秒
坑 2:熔断阈值太低导致误触发
# ❌ 错误:熔断阈值太低,容易误触发
sentinel:
degrade:
order-service:
grade: 2 # 异常比例
count: 0.1 # 10% 就熔断,太敏感
time-window: 10 # 10 秒恢复
# ✅ 正确:熔断阈值要合理
sentinel:
degrade:
order-service:
grade: 2 # 异常比例
count: 0.5 # 50% 才熔断
time-window: 30 # 30 秒恢复
min-request-count: 10 # 至少 10 个请求才统计
坑 3:熔断后不恢复
// ❌ 错误:熔断后永久打开
// 问题:没有处理 BlockException
@SentinelResource(value = "createOrder")
public Order createOrder(String userId) {
return orderService.create(userId);
}
// ✅ 正确:处理限流/熔断返回
@SentinelResource(value = "createOrder",
blockHandler = "createOrderBlock",
fallback = "createOrderFallback")
public Order createOrder(String userId) {
return orderService.create(userId);
}
// 限流/熔断处理
public Order createOrderBlock(String userId, BlockException ex) {
return Order.fallback("系统繁忙,请稍后再试");
}
// 业务异常处理
public Order createOrderFallback(String userId, Throwable ex) {
return Order.fallback("创建订单失败:" + ex.getMessage());
}
坑 4:热点参数限流不生效
// ❌ 错误:热点参数限流没有生效
@SentinelResource(value = "getOrder")
public Order getOrder(Long orderId) {
return orderService.getById(orderId);
}
// ✅ 正确:使用注解指定参数索引
@SentinelResource(value = "getOrder",
hotspotHandler = "getOrderHotspot")
public Order getOrder(Long orderId) {
return orderService.getById(orderId);
}
// 热点参数处理
public void getOrderHotspot(Long orderId, BlockException ex) {
// 参数限流触发
throw new BusinessException("访问过于频繁,请稍后再试");
}
坑 5:规则持久化问题
# ❌ 错误:规则只存在内存,应用重启后丢失
sentinel:
eager: true # 只是启动时加载
# 问题:重启后需要手动重新配置
# ✅ 正确:使用 Nacos/Apollo 持久化
sentinel:
datasource:
ds:
nacos:
server-addr: localhost:8848
data-id: sentinel-rules
group-id: SENTINEL_GROUP
data-type: json
rule-type: flow
八、实战:Spring Cloud Alibaba Sentinel
8.1 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
8.2 配置限流规则
@Configuration
public class SentinelConfig {
@PostConstruct
public void initRules() {
// 流控规则
FlowRule flowRule = new FlowRule("createOrder");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS 模式
flowRule.setCount(100); // 每秒 100 个请求
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
FlowRuleManager.loadRules(Collections.singletonList(flowRule));
// 熔断规则
DegradeRule degradeRule = new DegradeRule("createOrder");
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); // 异常比例
degradeRule.setCount(0.5); // 50% 异常触发熔断
degradeRule.setTimeWindow(10); // 10 秒后恢复
degradeRule.setMinRequestCount(10); // 至少 10 个请求
DegradeRuleManager.loadRules(Collections.singletonList(degradeRule));
}
}
8.3 使用注解定义资源
@Service
public class OrderService {
@SentinelResource(value = "createOrder",
blockHandler = "createOrderBlock",
fallback = "createOrderFallback")
public Order createOrder(String userId, String productId) {
return orderMapper.create(userId, productId);
}
// 限流/熔断时的处理
public Order createOrderBlock(String userId, String productId, BlockException ex) {
log.warn("createOrder 被限流/熔断: {}", ex.getMessage());
return Order.fallback("系统繁忙,请稍后再试");
}
// 业务异常时的处理
public Order createOrderFallback(String userId, String productId, Throwable ex) {
log.error("createOrder 业务异常: {}", ex.getMessage());
return Order.fallback("创建订单失败");
}
}
九、Sentinel vs Hystrix vs Resilience4j
| 特性 | Sentinel | Hystrix | Resilience4j |
|---|---|---|---|
| 隔离策略 | 信号量 | 线程池 | 线程池/信号量 |
| 熔断策略 | 慢调用/异常比例/异常数 | 失败比例 | 失败比例/数量 |
| 统计方式 | 滑动窗口 | 滚动窗口 | 滑动窗口 |
| 规则配置 | 控制台/API/Nacos | 配置中心 | 配置中心 |
| 维护状态 | 活跃 | 停止 | 活跃 |
| 生态 | SpringCloud Alibaba | SpringCloud | Spring Boot |
十、面试高频问题
Q1:Sentinel 和 Hystrix 有什么区别?
主要区别:
1. 隔离策略
- Hystrix:线程池隔离(每个依赖一个线程池)
- Sentinel:信号量隔离(轻量,性能更好)
2. 熔断策略
- Hystrix:失败比例
- Sentinel:慢调用比例 + 异常比例 + 异常数
3. 统计方式
- Hystrix:滚动窗口
- Sentinel:滑动窗口(更精确)
4. 规则配置
- Hystrix:只能内存存储
- Sentinel:支持 Nacos/Apollo/Zookeeper 等
5. 社区状态
- Hystrix:已停止维护(2020)
- Sentinel:活跃维护(阿里)
加分回答:
为什么 Sentinel 用信号量而不是线程池?
1. 性能更好:不需要线程切换
2. 资源占用少:不需要维护线程池
3. 实现简单:更适合单机限流
但信号量有个问题:
- 不能异步执行
- 如果下游超时,调用线程会阻塞
解决:结合异步框架(WebFlux)可以避免阻塞
Q2:熔断和限流的区别?
┌─────────────────────────────────────────────────────────────────┐
│ 流量控制:保护自己 │
│ - 触发条件:自己 QPS 超限 │
│ - 目的:防止被流量冲垮 │
│ - 效果:部分请求被拒绝 │
├─────────────────────────────────────────────────────────────────┤
│ 熔断降级:保护下游 │
│ - 触发条件:下游响应慢/异常多 │
│ - 目的:防止调用下游导致自己崩溃 │
│ - 效果:暂时不调用下游,快速失败 │
└─────────────────────────────────────────────────────────────────┘
关系:两者配合使用
- 限流:限制进入系统的流量
- 熔断:防止调用下游导致的级联故障
Q3:如何设计一个限流算法?
// 限流算法实现
public class RateLimiter {
// 计数器算法(简单,有临界问题)
public static boolean counter(long timestamp, int maxQps) {
if (timestamp != lastTimestamp) {
counter = 0;
lastTimestamp = timestamp;
}
return ++counter <= maxQps;
}
// 滑动窗口算法(精确,复杂)
public static boolean slidingWindow(long timestamp, int maxQps) {
// 统计最近 1 秒内的请求数
long count = getCountInLastSecond(timestamp);
return count < maxQps;
}
// 令牌桶算法(允许突发)
public static boolean tokenBucket(long timestamp, int rate) {
// 添加令牌
long tokens = Math.min(maxTokens,
lastTokens + (timestamp - lastRefillTime) * rate / 1000);
if (tokens >= 1) {
lastTokens = tokens - 1;
lastRefillTime = timestamp;
return true;
}
return false;
}
}
Q4:熔断器是如何工作的?
熔断器三状态:
1. CLOSED(关闭状态)
- 所有请求都通过
- 统计失败请求
- 如果失败达到阈值 → OPEN
2. OPEN(熔断状态)
- 所有请求都拒绝
- 等待 timeWindow
- 到达时间 → HALF_OPEN
3. HALF_OPEN(半开状态)
- 允许部分请求通过
- 如果成功 → CLOSED
- 如果失败 → OPEN
设计要点:
- 失败计数要有时间窗口(避免永久熔断)
- 半开状态要限制请求数(避免突然涌入)
- 状态变更要持久化(避免重启后状态丢失)
十一、核心要点总结
更多推荐




所有评论(0)