Lesson 69 · 分布式系统设计
限流、熔断与降级:Sentinel 与 Hystrix 原理
第 1 站
面试官:限流算法有哪些?熔断器怎么工作?
"大促时你们系统怎么做限流?令牌桶和滑动窗口有什么区别?熔断器的三种状态怎么切换?Sentinel 和 Hystrix 有什么区别?" —— 面试官想考察的是你对系统稳定性保障体系的理解深度。
在分布式系统中,流量是不可预测的。大促、热点事件、爬虫攻击都可能导致流量激增,压垮后端服务。限流、熔断、降级是保护系统的三道防线:
稳定性三板斧
限流:控制入口流量,防止过载
熔断:下游服务异常时快速失败,防止级联故障
降级:非核心功能让路给核心功能,保证系统基本可用
限流:控制入口流量,防止过载
熔断:下游服务异常时快速失败,防止级联故障
降级:非核心功能让路给核心功能,保证系统基本可用
三者的关系:
第 2 站
四种限流算法对比
| 算法 | 原理 | 优势 | 劣势 |
|---|---|---|---|
| 固定窗口 | 固定时间窗口内计数,超过阈值拒绝 | 实现简单 | 窗口边界突发(临界问题) |
| 滑动窗口 | 将窗口拆分为多个小格,滑动统计 | 平滑度更高 | 精度取决于格子数 |
| 漏桶 | 请求入桶,固定速率流出 | 输出速率恒定 | 无法处理突发流量 |
| 令牌桶 | 固定速率放令牌,有令牌才能通过 | 允许突发流量 | 令牌堆积可能瞬间放行大量请求 |
令牌桶 vs 滑动窗口 · 核心区别
// 令牌桶(Token Bucket)—— Sentinel 流控的核心
速率:每秒产生 10 个令牌(QPS = 10)
桶容量:最多存 20 个令牌(允许突发 20 个请求)
// 请求来了 → 取令牌 → 有令牌则通过 → 没令牌则拒绝
// 滑动窗口(Sliding Window)—— Sentinel 统计的核心
窗口:1 秒,拆分为 2 个格子(每个 500ms)
// 每个格子记录通过/拒绝的请求数
// 统计时只看当前时间往前 1 秒内的格子
// 格子越多精度越高,但内存开销也越大
第 3 站
熔断器:关闭/打开/半开三态
熔断器模式(Circuit Breaker Pattern)来自 Martin Fowler 的《Release It!》一书。其核心思想借鉴了电路中的保险丝:
CircuitBreaker.java · 熔断器核心逻辑
public class CircuitBreaker {
enum State { CLOSED, OPEN, HALF_OPEN }
private State state = State.CLOSED;
private int failureCount = 0;
private int successCount = 0;
private long lastFailureTime;
private final int FAILURE_THRESHOLD = 5; // 失败次数阈值
private final long OPEN_TIMEOUT = 10000; // 熔断持续时间(ms)
private final int HALF_OPEN_REQUESTS = 3; // 半开状态探测请求数
public <T> T execute(Supplier<T> supplier) {
switch (state) {
case OPEN:
if (System.currentTimeMillis() - lastFailureTime > OPEN_TIMEOUT) {
state = State.HALF_OPEN; // 超时了,转为半开
} else {
throw new CircuitBreakerOpenException(); // 快速失败
}
case HALF_OPEN:
try {
T result = supplier.get();
successCount++;
if (successCount >= HALF_OPEN_REQUESTS) {
reset(); // 探测成功,恢复关闭
}
return result;
} catch (Exception e) {
state = State.OPEN; // 探测失败,回到打开
lastFailureTime = System.currentTimeMillis();
throw e;
}
case CLOSED:
default:
try {
return supplier.get();
} catch (Exception e) {
failureCount++;
if (failureCount >= FAILURE_THRESHOLD) {
state = State.OPEN; // 失败超阈值,熔断
lastFailureTime = System.currentTimeMillis();
}
throw e;
}
}
}
}
第 4 站
Sentinel vs Hystrix 对比
Hystrix 是 Netflix 开源的熔断框架,Sentinel 是阿里巴巴开源的流量控制框架。两者定位相似,但设计哲学不同:
| 维度 | Hystrix | Sentinel |
|---|---|---|
| 隔离策略 | 线程池隔离 / 信号量隔离 | 信号量隔离(轻量) |
| 熔断策略 | 异常比例 / 异常数 | 异常比例 / 异常数 / 慢调用比例 |
| 限流 | 有限(QPS + 线程数) | 丰富(QPS、线程数、热点参数) |
| 实时指标 | RxJava 滑动窗口 | 滑动窗口 + LeapArray |
| 动态规则 | 多数据源支持 | 多数据源支持(Nacos/Apollo) |
| 控制台 | 简单监控 | 丰富控制台 + 实时监控 |
| 维护状态 | 已停止维护(2018) | 活跃维护中 |
| 性能影响 | 线程池隔离有额外开销 | 轻量,性能损耗小 |
Hystrix 的线程池隔离 · 核心设计
// Hystrix 的隔离策略:每个服务调用一个独立线程池
优点:
服务 A 的线程池满了 → 只影响对 A 的调用
→ 不会拖垮服务 B 的调用
→ 实现了舱壁隔离(Bulkhead)
缺点:
每个调用都需要线程切换(context switch)
→ 在高并发下开销很大
→ 线程池参数(核心线程数、最大线程数、队列大小)需要调优
// Sentinel 的选择:信号量隔离
优点:
不需要线程切换,在当前线程执行
→ 性能更好
→ 通过计数器(AtomicInteger)控制并发数
缺点:
无法像线程池那样强制中断超时请求
→ 需要依赖框架级别的超时机制
选型建议
新项目直接用 Sentinel。Hystrix 已经停止维护,Spring Cloud 官方也推荐使用 Spring Cloud CircuitBreaker(Resilience4j)替代 Hystrix。第 5 站
降级策略:Fallback 与优雅降级
降级是在系统压力过大时,主动放弃非核心功能,保证核心链路可用的策略。
降级策略分类
1. 自动降级(熔断触发)
下游服务熔断 → 自动执行 Fallback 逻辑
// 例:推荐服务挂了 → 返回默认推荐列表
2. 手动降级(开关控制)
运维通过配置中心关闭非核心功能
// 例:大促时关闭"猜你喜欢"、"个性化广告"
3. 超时降级
调用超时 → 返回缓存数据或默认值
// 例:价格服务超时 → 返回上次缓存的价格
4. 限流降级
超出限流阈值的请求 → 返回友好提示
// 例:"当前排队人数较多,请稍后再试"
Sentinel Fallback 示例
// Sentinel 的降级回调(BlockHandler + Fallback)
@SentinelResource(
value = "getProductDetail",
blockHandler = "getProductDetailBlock", // 限流/熔断时的处理
fallback = "getProductDetailFallback" // 业务异常时的处理
)
public ProductDetail getProductDetail(Long productId) {
return productService.getById(productId);
}
// 限流/熔断时:返回友好提示
public ProductDetail getProductDetailBlock(Long productId, BlockException ex) {
return ProductDetail.defaultInstance(); // 默认商品详情
}
// 业务异常时:返回缓存数据
public ProductDetail getProductDetailFallback(Long productId, Throwable t) {
return cacheService.getProductDetail(productId); // Redis 缓存
}
第 6 站
Sentinel 热点参数限流
常规限流是对"接口级别"做 QPS 限制,但很多时候需要对某个参数的特定值做限流:
热点参数限流场景
// 场景一:防止某个热门商品被疯狂刷单
// 接口:GET /product/{productId}
// 规则:productId = "爆款A" 时,QPS 不超过 100
// 其他 productId 正常限流 QPS = 1000
// 场景二:防止某个用户频繁调用接口
// 接口:POST /api/query
// 规则:userId = "恶意用户X" 时,QPS 不超过 5
// 场景三:大促秒杀
// 接口:POST /seckill/{skuId}
// 规则:每个用户(userId)每秒最多 1 次秒杀请求
@SentinelResource(value = "getProduct")
public Product getProduct(
@SentinelParam String productId, // 第一个参数
@SentinelParam String userId // 第二个参数
) {
return productService.getById(productId);
}
// 控制台配置热点规则:
// 资源名:getProduct
// 参数索引:0(productId)
// 单机阈值:1000
// 例外项:productId = "爆款A" → 阈值 100
热点参数限流的底层原理:Sentinel 为每个热点参数值维护一个独立的滑动窗口,统计该值的访问次数。当某个参数值的 QPS 超过阈值时,只限流该值,不影响其他值。
Sentinel 流控效果:
Sentinel 流控行为 · 三种控制方式
1. 快速失败(默认)
超过阈值的请求 → 直接抛 FlowException
// 适合:接口幂等、可以重试的场景
2. Warm Up(预热模式)
冷启动:系统刚启动时阈值较低,逐渐升高到设定值
// 原理:JIT 编译需要时间,刚启动时性能低于稳态
// 预热时长:默认 10 秒,期间阈值从 1/3 逐渐升高到全量
// 适合:秒杀开始时避免瞬间流量压垮系统
3. 排队等待(匀速排队)
超过阈值的请求 → 进入等待队列 → 按固定间隔放行
// 原理:漏桶算法,保证请求均匀通过
// 超时时间:排队超过 maxQueueingTimeMs → 拒绝
// 适合:脉冲流量场景,把突发请求"拉平"
// Sentinel 流控规则配置示例
FlowRule rule = new FlowRule();
rule.setResource("getProduct");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 按 QPS 限流
rule.setCount(1000); // 阈值 1000 QPS
rule.setControlBehavior( // 流控行为
RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // 匀速排队
rule.setMaxQueueingTimeMs(2000); // 最多排队 2 秒
第 7 站
系统自适应保护与总结
Sentinel 的系统自适应保护是最有特色的功能之一。它不是针对某个接口,而是根据整个系统的负载来做全局流控:
系统保护规则 · Sentinel SystemRule
// 系统保护的五种触发条件(任一满足即触发限流)
1. Load 保护
系统 Load > 阈值(如 5.0)时触发
→ 适用于 Linux 系统
2. CPU 使用率保护
CPU 使用率 > 阈值(如 80%)时触发
→ 最常用,响应最灵敏
3. 总线程数保护
并发线程数 > 阈值(如 500)时触发
→ 防止线程数过多导致上下文切换开销过大
4. 入口 QPS 保护
总入口 QPS > 阈值时触发
→ 全局流量控制
5. 平均 RT 保护
平均响应时间 > 阈值(如 200ms)时触发
→ 响应变慢说明系统已经过载
本课文脑图回顾
- 限流四种算法:固定窗口(简单但有临界问题)→ 滑动窗口(平滑)→ 漏桶(匀速)→ 令牌桶(允许突发)
- 熔断器三态:Closed(正常放行)→ Open(快速失败)→ Half-Open(探测恢复)
- Sentinel vs Hystrix:Sentinel 更轻量、功能更丰富、还在维护;新项目用 Sentinel
- 降级策略:自动降级(熔断触发)、手动降级(开关控制)、超时降级、限流降级
- 热点参数限流:对参数的特定值做独立限流,防止热门资源被打爆
- 系统自适应保护:根据 Load/CPU/线程数/QPS/RT 动态调整,借鉴 TCP BBR 算法