Skip to content
第 26 章 架构 ⏱ 12 分钟阅读

第 26 章:限流与降级 ​

学习目标 ​

  • 掌握限流算法:计数器、滑动窗口、令牌桶、漏桶
  • 学会用 Sentinel 限流
  • 降级策略与开关设计
  • 避免误杀、降级恢复的坑

一、限流算法 ​

1. 固定窗口计数器 ​

java
public class FixedWindow {
    private final long windowSize;  // 1 秒
    private final long maxCount;    // 100
    private AtomicLong count = new AtomicLong();
    private long windowStart = System.currentTimeMillis();

    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        if (now - windowStart >= windowSize) {
            windowStart = now;
            count.set(0);
        }
        return count.incrementAndGet() <= maxCount;
    }
}
text
问题:窗口边界突刺
00:00:00 - 00:00:01 通过 100
00:00:01 - 00:00:02 通过 100
实际 2 秒通过 200,但窗口内分别合规

2. 滑动窗口 ​

java
public class SlidingWindow {
    private final long windowSize;
    private final long maxCount;
    private final Queue<Long> requests = new ArrayDeque<>();

    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        while (!requests.isEmpty() && now - requests.peek() > windowSize) {
            requests.poll();
        }
        if (requests.size() < maxCount) {
            requests.offer(now);
            return true;
        }
        return false;
    }
}

3. 令牌桶 ​

java
public class TokenBucket {
    private final long capacity;
    private final long refillRate;  // 每秒补充
    private long tokens;
    private long lastRefill;

    public synchronized boolean tryAcquire() {
        long now = System.nanoTime();
        long elapsed = now - lastRefill;
        tokens = Math.min(capacity, tokens + elapsed * refillRate / 1_000_000_000);
        lastRefill = now;

        if (tokens > 0) {
            tokens--;
            return true;
        }
        return false;
    }
}
text
令牌桶 vs 漏桶
├── 令牌桶:允许突发(攒够令牌一波)
└── 漏桶:匀速(削峰填谷)

⚠️ 坑 1:秒杀场景用漏桶,瞬间峰值请求被匀速化,体验差。令牌桶允许突发,适合秒杀。

二、Sentinel 限流 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yaml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: sentinel-dashboard:8080
      datasource:
        flow:
          nacos:
            server-addr: nacos:8848
            dataId: order-flow-rules
java
// 1. 注解方式
@SentinelResource(value = "createOrder", blockHandler = "handleBlock")
public Order createOrder(OrderDto dto) {
    return orderService.create(dto);
}

public Order handleBlock(OrderDto dto, BlockException ex) {
    log.warn("触发限流:{}", dto);
    throw new RateLimitException("请求太频繁,请稍后再试");
}

// 2. 编程方式
FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100);   // QPS 100
FlowRuleManager.loadRules(List.of(rule));
java
// 3. 多种限流策略
// QPS 限流
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);

// 并发线程数限流
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);

// 关联限流(下单限流时也限支付)
rule.setStrategy(RuleConstant.STRATEGY_RELATE);
rule.setRefResource("payOrder");

三、限流维度 ​

yaml
# 1. 全局限流(总入口)
- resource: /api/order
  count: 1000

# 2. 用户级限流
- resource: createOrder
  count: 10          # 每用户 10 QPS
  limitApp: userId

# 3. IP 限流
- resource: /api/*
  count: 100
  limitApp: ip

# 4. 业务限流
- resource: seckill
  count: 10000
java
// 基于注解 + 自定义 Key
@SentinelResource(value = "createOrder", blockHandler = "handleBlock")
public Order createOrder(@RequestHeader("X-User-Id") String userId, OrderDto dto) {
    ContextUtil.enter("createOrder", userId);   // 用户维度限流
    return orderService.create(dto);
}

⚠️ 坑 2:限流规则写死在代码,改阈值要发版。用 Nacos 动态配置,热更新生效。

四、热点参数限流 ​

java
// 秒杀:某个商品限流
@SentinelResource(value = "seckill", blockHandler = "handleBlock")
public SeckillResult seckill(@RequestParam Long skuId) {
    return seckillService.execute(skuId);
}
yaml
# 规则:skuId=100 的 QPS 限 1000
ParamFlowRule rule = new ParamFlowRule("seckill")
    .setParamIdx(0)
    .setGrade(RuleConstant.FLOW_GRADE_QPS)
    .setCount(10000);
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(100L))
    .setClassType(Long.class.getName())
    .setCount(1000);
rule.setParamFlowItemList(List.of(item));

五、降级策略 ​

java
// 1. 慢调用降级
@SentinelResource(value = "queryUser", fallback = "queryUserFallback")
public User queryUser(Long id) {
    return userRepository.findById(id);
}

public User queryUserFallback(Long id) {
    return new User(id, "默认用户", "default");
}

// 熔断规则
DegradeRule rule = new DegradeRule("queryUser")
    .setGrade(RuleConstant.DEGRADE_GRADE_RT)            // 慢调用比例
    .setCount(100)                                       // 阈值 100ms
    .setTimeWindow(10)                                   // 10s 熔断窗口
    .setMinRequestAmount(100)                            // 最小请求数
    .setSlowRatioThreshold(0.5);                         // 50% 慢调用
java
// 2. 异常比例降级
DegradeRule rule = new DegradeRule("queryUser")
    .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO)
    .setCount(0.5)        // 异常率 50%
    .setTimeWindow(10);

// 3. 异常数降级
DegradeRule rule = new DegradeRule("queryUser")
    .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT)
    .setCount(100)        // 异常数 100
    .setTimeWindow(10);

六、降级开关 ​

java
// 1. 业务开关(配置中心)
@Value("${feature.seckill.enabled:true}")
private boolean seckillEnabled;

public SeckillResult seckill(Long skuId) {
    if (!seckillEnabled) {
        return SeckillResult.fail("活动已结束");
    }
    // ...
}

// 2. Sentinel 开关
@SentinelResource(value = "seckill", fallback = "fallback")
public SeckillResult seckill(Long skuId) {
    // ...
}

public SeckillResult fallback(Long skuId) {
    return SeckillResult.fail("服务繁忙,请稍后再试");
}
yaml
# 3. Nacos 动态开关
@Configuration
@NacosConfigurationProperties(dataId = "feature-switch", group = "FEATURE")
public class FeatureSwitch {
    private boolean seckillEnabled = true;
    private boolean refundEnabled = true;
}

⚠️ 坑 3:降级 fallback 返回 null,调用方没处理 = NPE。降级必须有兜底逻辑。

七、限流与降级配合 ​

java
// 优先级:限流 > 降级
// 限流:超出容量直接拒绝
// 降级:服务异常时返回兜底

// 实战:双十一
// 1. 限流:入口限制 100 万 QPS
// 2. 降级:非核心服务返回缓存
// 3. 熔断:依赖服务故障时隔断
java
// 完整防护
@SentinelResource(
    value = "createOrder",
    blockHandler = "handleBlock",      // 限流 / 熔断
    fallback = "handleFallback"        // 异常
)
public Order createOrder(OrderDto dto) {
    if (!featureSwitch.isOrderEnabled()) {
        return Order.fallback();
    }
    return orderService.create(dto);
}

八、限流集群 ​

yaml
# 单机限流:每个节点 100 QPS
# 集群节点 10 个 → 总 1000 QPS 但任一节点可能成为瓶颈

# Sentinel 集群限流:统一管理
spring:
  cloud:
    sentinel:
      transport:
        dashboard: sentinel-dashboard:8080
        heartbeat-interval-ms: 5000
java
// Token Server 统一分配
// 各节点向 Token Server 请求令牌

本章小结 ​

算法特点
固定窗口简单,边界突刺
滑动窗口精确,内存开销
令牌桶允许突发
漏桶削峰填谷
工具特点
Sentinel阿里,功能全
Resilience4jSpring 官方
Guava RateLimiter简单,单机
策略用途
QPS入口流控
并发线程慢服务保护
热点参数秒杀
慢调用降级兜底

动手练习 ​

  1. 令牌桶:用代码实现令牌桶,模拟突发流量
  2. Sentinel 集成:在订单接口加限流 100 QPS,触发后返回兜底
  3. 热点限流:秒杀接口按 skuId 限流,每个商品 1000 QPS
  4. 降级演示:故意让 user 服务慢调用,触发熔断,观察 fallback

下一章:第 27 章:熔断与隔离 →

本站基于 VitePress 构建 · 由 StackHub 团队维护