Java 面试笔记VII · 04 / 05

Lesson 69 · 分布式系统设计

限流、熔断与降级:Sentinel 与 Hystrix 原理

高级·#高可用·#限流·#熔断

第 1 站

面试官:限流算法有哪些?熔断器怎么工作?

"大促时你们系统怎么做限流?令牌桶和滑动窗口有什么区别?熔断器的三种状态怎么切换?Sentinel 和 Hystrix 有什么区别?" —— 面试官想考察的是你对系统稳定性保障体系的理解深度。

在分布式系统中,流量是不可预测的。大促、热点事件、爬虫攻击都可能导致流量激增,压垮后端服务。限流、熔断、降级是保护系统的三道防线:

稳定性三板斧
限流:控制入口流量,防止过载
熔断:下游服务异常时快速失败,防止级联故障
降级:非核心功能让路给核心功能,保证系统基本可用

三者的关系:

系统稳定性的三道防线 限流 流量控制 拒绝超量请求 熔断 快速失败 防止级联雪崩 降级 功能降级 保证核心可用 保护自身 保护调用链 保护系统整体 限流是入口保护 → 熔断是调用保护 → 降级是全局保护
图 1限流、熔断、降级:系统稳定性的三道防线
第 2 站

四种限流算法对比

算法原理优势劣势
固定窗口固定时间窗口内计数,超过阈值拒绝实现简单窗口边界突发(临界问题)
滑动窗口将窗口拆分为多个小格,滑动统计平滑度更高精度取决于格子数
漏桶请求入桶,固定速率流出输出速率恒定无法处理突发流量
令牌桶固定速率放令牌,有令牌才能通过允许突发流量令牌堆积可能瞬间放行大量请求
四种限流算法可视化对比 固定窗口计数器 窗口 1: 8/10 窗口 2: 5/10 边界问题:窗口1末尾+窗口2开头可能突发20 滑动窗口 ← 滑动 → 窗口随时间滑动,平滑度更高 漏桶算法 水(请求) 请求入桶 ↓ 固定速率流出 适合:平滑流量 令牌桶算法 固定速率放令牌 有令牌 → 放行 无令牌 → 拒绝/排队 适合:允许突发流量 Sentinel 使用滑动窗口 + 令牌桶;Guava RateLimiter 使用令牌桶
图 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!》一书。其核心思想借鉴了电路中的保险丝:

熔断器三种状态及切换条件 Closed 关闭 正常放行请求 统计失败率 failures < threshold Open 打开 直接拒绝请求 不调用下游服务 快速失败 Half-Open 半开 放行少量探测请求 检测下游是否恢复 timeout 后进入 失败率 超过阈值 等待超时 后进入 探测请求成功 → 恢复为 Closed 探测请求失败 → 回到 Open 典型参数:失败率阈值 50%,超时时间 10s,探测请求数 3
图 3熔断器三种状态:Closed(正常)→ Open(熔断)→ Half-Open(探测)
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 是阿里巴巴开源的流量控制框架。两者定位相似,但设计哲学不同:

维度HystrixSentinel
隔离策略线程池隔离 / 信号量隔离信号量隔离(轻量)
熔断策略异常比例 / 异常数异常比例 / 异常数 / 慢调用比例
限流有限(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 缓存
}
电商系统降级策略示例 核心链路 下单、支付、库存 次要链路 推荐、评价、活动 非核心 广告、个性化、埋点 可关闭 日志采集、数据分析 流量压力从右向左递增 → 优先关闭右侧非核心功能 大促预案:关闭广告/推荐 → 关闭评价/活动 → 只保留下单/支付/库存 降级开关通过配置中心(Nacos/Apollo)实时推送,秒级生效
图 4降级策略:按业务重要性分级,压力大时优先关闭非核心功能
第 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)时触发
   → 响应变慢说明系统已经过载
Sentinel 系统自适应保护原理 系统指标采集 Load / CPU / 线程 / QPS / RT BBR 算法评估 估算系统最大容量 动态调整阈值 自动限流/放行 系统稳定 BBR(Bottleneck Bandwidth and Round-trip) 借鉴 TCP BBR 拥塞控制算法,通过 maxQPS × minRT 估算系统最大容量
图 5Sentinel 系统自适应保护:采集系统指标,动态调整限流阈值
本课文脑图回顾
  • 限流四种算法:固定窗口(简单但有临界问题)→ 滑动窗口(平滑)→ 漏桶(匀速)→ 令牌桶(允许突发)
  • 熔断器三态:Closed(正常放行)→ Open(快速失败)→ Half-Open(探测恢复)
  • Sentinel vs Hystrix:Sentinel 更轻量、功能更丰富、还在维护;新项目用 Sentinel
  • 降级策略:自动降级(熔断触发)、手动降级(开关控制)、超时降级、限流降级
  • 热点参数限流:对参数的特定值做独立限流,防止热门资源被打爆
  • 系统自适应保护:根据 Load/CPU/线程数/QPS/RT 动态调整,借鉴 TCP BBR 算法