限流降级熔断场景

2021年618大促,某电商平台的商品详情页接口突然全面崩溃,所有用户都无法访问商品。

技术团队排查后发现:商品详情页依赖了促销服务,大促期间促销服务响应缓慢。本来应该限流保护促销服务,但限流配置写错了——限流阈值设成了1次/秒,导致正常请求也被限流了。

更糟糕的是:没有熔断机制,大促流量全部打到促销服务,导致促销服务崩溃,进而拖垮了商品详情页服务。

这次故障持续了15分钟,影响了大促GMV约500万元。

【面试官手记】

限流降级熔断是保障系统稳定性的三板斧。我面试过的候选人里,能说清楚"限流算法"的不超过30%,能说清楚"熔断器模式"的不超过20%。三者的关键是理解保护的是谁

一、限流、降级、熔断的区别 🔴

1.1 三个概念

限流(Rate Limiting):
- 目的:保护系统不被过载
- 手段:拒绝超出阈值的请求
- 关键词:阈值、拒绝

降级(Degradation):
- 目的:保护核心功能
- 手段:关闭非核心功能
- 关键词:开关、返回兜底数据

熔断(Circuit Breaker):
- 目的:防止故障扩散
- 手段:快速失败,不再调用下游
- 关键词:状态、恢复

1.2 三者关系

关系图:

请求 → [限流] → [熔断] → [降级] → 业务逻辑

限流:保护自己不被压垮
熔断:保护不被下游拖垮
降级:保证核心功能可用

执行顺序:限流 → 熔断 → 降级

1.3 面试追问

面试官:限流、降级、熔断分别是什么?

候选人:三个概念:

限流是保护系统不被过载,超出阈值的请求直接拒绝。

降级是关闭非核心功能,返回兜底数据,保证核心功能可用。

熔断是当下游服务故障时,快速失败不再调用,防止故障扩散。

【面试官心理】

三者的追问通常很深入。能说出三者区别的候选人,说明理解基本概念;能说出实现算法的候选人,说明有实践能力。

二、限流算法 🔴

2.1 计数器算法

计数器算法:

原理:统计单位时间内的请求数

实现:
- 用AtomicInteger计数
- 每秒重置计数器

问题:
- 临界问题:0:59和1:00的请求可能超出2倍
public class CounterRateLimiter {

    private int maxRequests;
    private long windowSizeMs;
    private AtomicInteger count;
    private AtomicLong windowStart;

    public boolean tryAcquire() {
        long now = System.currentTimeMillis();
        windowStart.set(now);

        if (count.get() >= maxRequests) {
            return false;
        }
        count.incrementAndGet();
        return true;
    }
}

2.2 滑动窗口算法

滑动窗口算法:

原理:将时间窗口分成多个小窗口,每过一个小窗口滑动一次

优点:解决计数器算法的临界问题
缺点:实现复杂
public class SlidingWindowRateLimiter {

    private int maxRequests;
    private int windowCount;
    private long windowSizeMs;
    private AtomicInteger[] counters;
    private AtomicLong windowStart;

    public boolean tryAcquire() {
        long now = System.currentTimeMillis();
        int index = (int) ((now / windowSizeMs) % windowCount);

        // 清理过期计数
        long start = windowStart.get();
        if (now - start >= windowSizeMs * windowCount) {
            counters[index].set(0);
            windowStart.set(now);
        }

        if (counters[index].get() >= maxRequests / windowCount) {
            return false;
        }
        counters[index].incrementAndGet();
        return true;
    }
}

2.3 令牌桶算法

令牌桶算法:

原理:令牌以固定速率放入桶中,请求需要获取令牌

优点:
- 支持突发流量
- 令牌可以积累

Redis实现:Redis4.0+支持Lua脚本
public class TokenBucketRateLimiter {

    private RedisTemplate<String, String> redisTemplate;
    private String key;
    private long rate;
    private long capacity;

    // Lua脚本保证原子性
    public boolean tryAcquire() {
        String script = """
            local key = KEYS[1]
            local rate = tonumber(ARGV[1])
            local capacity = tonumber(ARGV[2])
            local now = tonumber(ARGV[3])
            local requested = tonumber(ARGV[4])

            local bucket = tonumber(redis.call('GET', key) or capacity)
            if bucket < requested then
                return 0
            else
                local newBucket = math.max(0, bucket - requested)
                redis.call('SET', key, newBucket)
                redis.call('EXPIRE', key, 10)
                return 1
            end
            """;

        return redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            List.of(key),
            rate, capacity, System.currentTimeMillis(), 1
        ) == 1;
    }
}

2.4 漏桶算法

漏桶算法:

原理:请求进入桶中,以固定速率漏出

优点:流量平滑
缺点:不能支持突发流量

三、熔断器 🟡

3.1 熔断器三状态

熔断器三状态:

CLOSED(关闭):
- 正常调用下游
- 统计失败率
- 失败率超阈值 → OPEN

OPEN(打开):
- 快速失败,直接返回
- 超过熔断时间 → HALF_OPEN

HALF_OPEN(半开):
- 允许部分请求通过
- 请求成功 → CLOSED
- 请求失败 → OPEN

3.2 Sentinel实现

// Sentinel熔断配置
@Configuration
public class SentinelConfig {

    @Bean
    public RuleConfig ruleConfig() {
        List<FlowRule> rules = new ArrayList<>();

        FlowRule rule = new FlowRule("getProductDetail");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);  // 按QPS限流
        rule.setCount(1000);  // 每秒1000次
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);

        // 熔断规则
        DegradeRule degradeRule = new DegradeRule("getProductDetail");
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT_QPS);  // 按响应时间熔断
        degradeRule.setCount(200);  // 平均RT超过200ms
        degradeRule.setSlowRatioThreshold(0.8);  // 80%请求变慢
        degradeRule.setMinRequestAmount(5);  // 最小请求数
        degradeRule.setStatIntervalMs(1000);  // 统计时间窗口
        degradeRule.setTimeWindow(10);  // 熔断时间窗口

        return new RuleConfig();
    }
}

3.3 Sentinel注解

// 使用注解定义熔断规则
@SentinelResource(
    value = "getProductDetail",
    blockHandler = "getProductDetailBlock",
    fallback = "getProductDetailFallback"
)
public ProductDetail getProductDetail(Long productId) {
    return productService.getDetail(productId);
}

// 限流处理
public ProductDetail getProductDetailBlock(Long productId, BlockException e) {
    return ProductDetail.defaultInstance();  // 返回兜底
}

// 熔断/异常处理
public ProductDetail getProductDetailFallback(Long productId, Throwable e) {
    log.error("获取商品详情失败", e);
    return ProductDetail.fromCache(productId);  // 从缓存获取
}

四、降级策略 🟡

4.1 降级开关

// 降级开关实现
@Component
public class DegradeSwitch {

    @Value("${degrade.promotion:false}")
    private boolean promotionEnabled;

    public ProductDetail getProductDetail(Long productId) {
        // 如果促销功能降级,返回不含促销信息的数据
        if (!promotionEnabled) {
            return getBasicProductDetail(productId);
        }
        return getFullProductDetail(productId);
    }
}

4.2 降级配置

# Apollo配置中心
degrade:
  promotion: true          # 促销模块开关
  recommendation: true      # 推荐模块开关
  review: true             # 评价模块开关

# Spring读取
@ApolloConfig
private Config config;

public boolean isPromotionEnabled() {
    return config.getBooleanProperty("degrade.promotion", true);
}

4.3 降级返回

// 降级返回兜底数据
@RestController
public class ProductController {

    @GetMapping("/product/{id}")
    public Result<ProductVO> getProduct(@PathVariable Long id) {
        try {
            ProductDetail detail = productService.getDetail(id);
            return Result.success(toVO(detail));
        } catch (Exception e) {
            // 降级返回
            ProductVO vo = productCacheService.getFromCache(id);
            if (vo != null) {
                return Result.success(vo);  // 返回缓存数据
            }
            return Result.error("服务繁忙,请稍后重试");
        }
    }
}

五、生产避坑 🟡

5.1 限流降级熔断的五大坑

坑1:限流阈值设错了

问题:限流阈值设得太小,导致正常请求被限流
场景:大促期间误操作
解决方案:
- 限流前做压测
- 限流阈值可动态调整

坑2:没有熔断导致故障扩散

问题:下游服务挂了,但没有熔断,导致自己也被拖垮
场景:调用链中某个节点故障
解决方案:
- 必须配置熔断器
- 设置合理的超时时间

坑3:降级数据太假

问题:降级返回的数据太简单,用户体验差
场景:商品详情页降级后显示空
解决方案:
- 降级数据要尽量有用
- 可以返回缓存数据、历史数据

坑4:只保护自己不保护下游

问题:只做了入口限流,没做下游调用限流
场景:调用下游时压垮下游
解决方案:
- 每个下游调用都要限流
- 用Sentinel做资源级别限流

坑5:熔断后没有恢复

问题:熔断后一直保持熔断状态
场景:下游恢复后没检测到
解决方案:
- 配置合理的熔断时间窗口
- 半开状态探测下游是否恢复

5.2 配置参考

限流配置参考:

入口限流:
- QPS阈值:预估QPS × 0.8
- 限流策略:拒绝 + 友好提示

下游限流:
- QPS阈值:下游处理能力 × 0.8
- 超时时间:P99响应时间的2倍

熔断配置:
- 触发条件:失败率 > 50% 或 RT > 1s
- 熔断时长:30秒
- 半开探测:20%流量试探

六、真实面试回放 🟡

面试官:Sentinel和Hystrix的区别是什么?

候选人(小张):三个区别:

一是线程隔离。Hystrix用线程池隔离,会消耗线程资源;Sentinel用信号量隔离,性能更好。

二是熔断策略。Hystrix只支持失败率熔断;Sentinel支持失败率、RT、异常数三种。

三是限流。Hystrix不支持限流;Sentinel原生支持限流。

面试官:令牌桶和漏桶的区别是什么?

小张:两个区别:

一是流量形状。令牌桶支持突发流量,突发时拿积累的令牌;漏桶不支持突发,流量始终平滑。

二是适用场景。令牌桶适合需要处理突发流量的场景;漏桶适合需要流量绝对平滑的场景。

面试官:熔断器怎么设计?

小张:三个状态:

Closed状态下正常调用,统计失败率。

失败率超阈值后进入Open状态,快速失败。

熔断时间到了进入HalfOpen状态,放行部分请求试探。

试探成功回到Closed,失败回到Open。

【面试官手记】

小张这场面试的亮点:

  1. 知道Sentinel和Hystrix的区别

  2. 知道令牌桶和漏桶的区别

  3. 知道熔断器三状态的设计

限流降级熔断是P6工程师必备技能,能完整回答的候选人,说明有高可用设计能力。

限流降级熔断的核心是保护系统稳定性。记住三个要点:

  1. 限流:保护自己,用令牌桶/滑动窗口
  2. 熔断:保护不被下游拖垮,用Sentinel
  3. 降级:保证核心功能,用降级开关

三板斧配合使用,才能保障系统稳定。