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

问题:

  1. 为什么数据库会崩溃?
  2. 为什么一个服务崩溃会导致整个系统崩溃?
  3. 如何防止雪崩?

二、典型场景:雪崩是如何发生的

2.1 故障链路图

故障传播

调用链

数据库1崩溃

等待|堆积|崩溃

正常

调用超时

熔断

网关

订单服务

用户服务

库存服务

数据库1

数据库2

用户服务超时

订单服务堆积

库存服务正常

网关超时

网关熔断

2.2 根因分析

没有

不合理

合理

没有

系统崩溃

为什么?

有限流吗?

所有请求都进入系统

限流规则合理吗?

限流阈值太高

熔断配置了吗?

下游失败 → 上游堆积

可能是其他问题

线程耗尽 → OOM

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 三大核心功能

保护对象

Sentinel 三大功能

限制 QPS/并发数

快速失败

全局维度

流量控制
Flow Control

熔断降级
Degrade

系统自适应
System Protection

保护自己

保护下游

保护全局

功能 保护对象 触发条件 效果
流量控制 自身 QPS/并发数超限 拒绝/排队/冷启动
熔断降级 下游 响应慢/异常多 快速失败
系统自适应 全局 CPU/LOAD/线程数 全局限流

四、流量控制(Flow Control)

4.1 流量控制策略

排队等待

请求

当前 QPS > 阈值?

排队

立即执行

冷启动(预热)

请求

当前 QPS < 阈值?

逐步增加

达到阈值,拒绝

直接拒绝(快速失败)

请求

QPS > 阈值?

直接拒绝

放行执行

策略 说明 适用场景
直接拒绝 超过阈值直接拒绝 允许部分失败
冷启动 逐步增加阈值 秒杀、预热
排队等待 超过阈值排队 削峰填谷

4.2 滑动窗口算法原理

当前 QPS

窗口4 (750-1000ms)

窗口3 (500-750ms)

窗口2 (250-500ms)

窗口1 (0-250ms)

累加

累加

累加

累加

1 秒时间窗口

0ms

250ms

500ms

750ms

1000ms

counter: 300

counter: 250

counter: 200

counter: 100

QPS = W1 + W2 + W3 + W4
= 300 + 250 + 200 + 100 = 850

// 滑动窗口核心实现
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 熔断器三种状态

初始状态

失败次数/比例达到阈值

等待时间到达(熔断时长)

请求成功(N 个请求后)

请求失败

Closed

Open

HalfOpen

正常状态
放行所有请求
统计失败

熔断状态
拒绝所有请求
不统计

半开状态
放行部分请求
试探恢复

5.2 三种熔断策略

异常数熔断

统计异常次数

异常次数 > 阈值?

触发熔断

异常比例熔断

统计异常

异常比例 > 阈值?

触发熔断

慢调用比例熔断

是且比例 > 50%

统计响应时间

平均响应时间 > 阈值?

触发熔断

策略 触发条件 说明 适用场景
慢调用比例 平均 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 职责

构建资源节点

统计实时数据

流量控制

熔断降级

系统保护

Sentinel SlotChain

NodeSelectorSlot

ClusterBuilderSlot

LogSlot

StatisticSlot

FlowSlot

DegradeSlot

SystemSlot

AuthoritySlot

Slot 职责
NodeSelectorSlot 构建资源的调用链节点
ClusterBuilderSlot 构建集群节点统计信息
LogSlot 记录日志
StatisticSlot 统计实时数据(QPS、RT、异常数)
FlowSlot 流量控制
DegradeSlot 熔断降级
SystemSlot 系统自适应保护
AuthoritySlot 黑白名单控制

6.2 请求处理流程

业务逻辑 DegradeSlot FlowSlot StatisticSlot NodeSelectorSlot 请求 业务逻辑 DegradeSlot FlowSlot StatisticSlot NodeSelectorSlot 请求 alt [触发限流] [通过] alt [触发熔断] [通过] 进入 SlotChain 构建资源节点 记录请求 更新计数器 流量检查 检查限流规则 抛出 FlowException 熔断检查 检查熔断状态 抛出 DegradeException 执行业务 返回结果

七、⚠️ 避坑指南:我踩过的 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

Resilience4j

Vavr 开源

响应式编程

轻量级

Hystrix

Netflix 开源

线程池隔离

停止维护

Sentinel(推荐)

阿里开源

滑动窗口统计

实时指标

活跃维护

特性 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

设计要点:
- 失败计数要有时间窗口(避免永久熔断)
- 半开状态要限制请求数(避免突然涌入)
- 状态变更要持久化(避免重启后状态丢失)

十一、核心要点总结

Sentinel
熔断限流

流量控制

滑动窗口算法

直接拒绝/冷启动/排队等待

QPS/并发数控制

熔断降级

熔断器状态机

CLOSED/OPEN/HALF_OPEN

慢调用比例/异常比例/异常数

SlotChain

NodeSelectorSlot

StatisticSlot

FlowSlot

DegradeSlot

SystemSlot

避坑指南

阈值设置合理

熔断要能恢复

处理 BlockException

规则持久化

对比选型

vs Hystrix

vs Resilience4j

Sentinel 优势


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐