第 69 章:限流与熔断
学习目标
- 理解限流、熔断、降级的区别与价值
- 掌握 Sentinel 核心规则与 Spring Boot 集成
- 学会热点参数限流、熔断降级的实战应用
一、为什么需要限流熔断?
三大场景:
- 限流(Rate Limiting):拒绝过多请求,保护系统不被压垮
- 熔断(Circuit Breaker):下游故障时快速失败,避免雪崩
- 降级(Degradation):系统压力大时,放弃非核心功能保核心
二、限流算法
1. 计数器(固定窗口)
java
// 简单粗暴:1 秒内超过 1000 个请求就拒绝
AtomicLong counter = new AtomicLong(0);
public boolean tryAcquire() {
long currentSecond = System.currentTimeMillis() / 1000;
if (currentSecond != lastSecond.get()) {
counter.set(0);
lastSecond.set(currentSecond);
}
return counter.incrementAndGet() <= 1000;
}问题:临界突刺。0.9 秒时来了 1000 个,1.0 秒又来了 1000 个,2 秒内就放过 2000 个。
2. 滑动窗口
java
// 维护最近 N 个时间窗的计数
Deque<Long> timestamps = new ArrayDeque<>();
public boolean tryAcquire() {
long now = System.currentTimeMillis();
long windowStart = now - 1000; // 1 秒窗口
// 移除过期时间戳
while (!timestamps.isEmpty() && timestamps.peekFirst() < windowStart) {
timestamps.pollFirst();
}
if (timestamps.size() < 1000) {
timestamps.add(now);
return true;
}
return false;
}3. 令牌桶(推荐)
java
// 桶里放 N 个令牌,每秒生成 R 个
// 拿令牌 → 没令牌就拒绝或等待
public class TokenBucket {
private final int capacity; // 桶容量
private final double rate; // 令牌生成速率(个/秒)
private double tokens; // 当前令牌数
private long lastRefillTime; // 上次填充时间
public synchronized boolean tryAcquire() {
refill();
if (tokens >= 1) {
tokens -= 1;
return true;
}
return false;
}
private void refill() {
long now = System.currentTimeMillis();
long elapsed = now - lastRefillTime;
tokens = Math.min(capacity, tokens + (elapsed / 1000.0) * rate);
lastRefillTime = now;
}
}4. 漏桶
java
// 请求进入漏桶,桶以固定速率漏水(处理)
// 桶满则拒绝(背压)
public class LeakyBucket {
private final int capacity;
private final double rate; // 处理速率
private double water; // 当前水量
public synchronized boolean tryAcquire() {
if (water >= capacity) return false;
water += 1;
return true;
}
public void process() {
water = Math.max(0, water - rate * 0.001);
}
}算法对比:
| 算法 | 特点 | 适用 |
|---|---|---|
| 计数器 | 简单,突刺问题 | 粗粒度限流 |
| 滑动窗口 | 精确,平滑 | API 网关 |
| 令牌桶 | 允许突发,平滑 | 最常用(Nginx、Guava) |
| 漏桶 | 强制恒定速率 | 流量整形 |
三、Sentinel 集成
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-datasource-nacos</artifactId>
</dependency>yaml
spring:
application:
name: taskflow
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台
port: 8719
datasource:
# ① 规则持久化(从 Nacos 拉取)
flow:
nacos:
server-addr: localhost:8848
data-id: taskflow-flow-rules
rule-type: flow
degrade:
nacos:
server-addr: localhost:8848
data-id: taskflow-degrade-rules
rule-type: degrade
web-context-unify: false # 关闭 context 合并
filter:
url-patterns: /*四、流量控制规则
java
@Service
public class OrderService {
@SentinelResource(
value = "createOrder", // ① 资源名
blockHandler = "createOrderBlockHandler", // ② 限流/降级时调用
fallback = "createOrderFallback" // ③ 业务异常时调用
)
public Long createOrder(OrderDTO dto) {
// 业务逻辑
return orderMapper.insert(order);
}
// 限流处理
public Long createOrderBlockHandler(OrderDTO dto, BlockException ex) {
log.warn("触发限流: {}", ex.getMessage());
throw new BusinessException(ErrorCode.RATE_LIMIT, "系统繁忙,请稍后再试");
}
// 降级处理
public Long createOrderFallback(OrderDTO dto, Throwable ex) {
log.error("业务异常", ex);
return -1L;
}
}五种流控模式
yaml
# application.yml(也可以通过控制台动态配置)
sentinel:
rules:
flow:
- resource: createOrder
grade: qps # ① 按 QPS 限流
count: 100 # 阈值:100 QPS
controlBehavior: reject # 直接拒绝
limitApp: default
- resource: createOrder
grade: qps
count: 50
controlBehavior: warm_up # ② 预热(冷启动)
warmUpPeriodSec: 10 # 10 秒预热到 50 QPS
- resource: createOrder
grade: qps
count: 100
controlBehavior: rate_limiter # ③ 排队等待
maxQueueingTimeMs: 5000 # 最长等 5 秒
- resource: getProduct
grade: thread # ④ 按并发线程数限流
count: 20
# 关联限流:下单触发支付限流
- resource: payOrder
grade: qps
count: 200
refResource: createOrder # ⑤ createOrder 触发时 payOrder 限流热点参数限流
yaml
# 针对特定参数限流:同一用户 ID 1 秒内最多 10 次请求
- resource: getUser
grade: qps
count: 100
paramFlowItem:
- object: String # 参数类型
paramIdx: 0 # 参数位置
count: 10 # 该参数值阈值
durationInSec: 1 # 时间窗口java
// Controller
@GetMapping("/user/{id}")
@SentinelResource("getUser")
public Result<UserVO> getUser(@PathVariable Long id) { ... }五、熔断降级规则
yaml
# 三种熔断策略
sentinel:
rules:
degrade:
# ① 慢调用比例:超过 1 秒的调用比例 > 50%,熔断 10 秒
- resource: callPayment
grade: rt # 慢调用比例
count: 1000 # 慢调用阈值(ms)
slowRatioThreshold: 0.5 # 慢调用比例
timeWindow: 10 # 熔断时长(秒)
minRequestAmount: 10 # 最小请求数(避免抖动)
statIntervalMs: 1000 # 统计窗口
# ② 异常比例:异常率 > 50% 熔断
- resource: callInventory
grade: exception_ratio
count: 0.5 # 异常比例阈值
timeWindow: 10
minRequestAmount: 10
# ③ 异常数:1 分钟内异常数 > 10 熔断
- resource: callLogistics
grade: exception_count
count: 10
timeWindow: 60
statIntervalMs: 60000熔断状态机
六、网关层限流
yaml
spring:
cloud:
gateway:
routes:
- id: user-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒允许 100 个请求
redis-rate-limiter.burstCapacity: 200 # 桶容量 200
redis-rate-limiter.requestedTokens: 1 # 每个请求消耗 1 个令牌
key-resolver: "#{@userKeyResolver}" # Key 解析器java
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("X-User-Id"))
.orElse(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress())
);
}七、自定义限流处理
java
@Component
public class SentinelBlockHandler {
// 全局兜底
public static Result<?> defaultBlockHandler(Object o, BlockException ex) {
log.warn("限流触发 resource={} rule={}",
ex.getResource(), ex.getRule());
return Result.fail(429, "系统繁忙,请稍后再试");
}
// 指定资源
public static Result<?> createOrderBlock(OrderDTO dto, BlockException ex) {
// 排队等待场景:返回客户端"请重试"
return Result.fail(429, "下单人数过多,请稍后再试");
}
}java
@SentinelResource(
value = "createOrder",
blockHandlerClass = SentinelBlockHandler.class,
blockHandler = "createOrderBlock",
fallbackClass = SentinelBlockHandler.class,
fallback = "defaultBlockHandler"
)
public Long createOrder(OrderDTO dto) { ... }八、限流维度对比
| 维度 | 网关层 | 服务层 | 方法层 |
|---|---|---|---|
| 位置 | Nginx / Gateway | 微服务入口 | Controller / Service |
| 粒度 | URL、IP、用户 | 服务、方法 | 参数值 |
| 适用 | 防刷、防爬虫 | 防雪崩 | 热点保护 |
| 工具 | Nginx limit_req / Gateway | Sentinel | Sentinel |
九、生产级限流配置示例
yaml
# 网关层:粗粒度,全局保护
spring.cloud.gateway.routes.*.filters.RequestRateLimiter:
replenishRate: 1000 # 全局 1000 QPS
burstCapacity: 2000
# 服务层:服务间调用的保护
@SentinelResource("callPayment")
- grade: qps
count: 200 # 支付服务最大 200 QPS
# 方法层:单个接口保护
@SentinelResource("seckill")
- grade: qps
count: 50 # 秒杀接口 50 QPS
# 热点参数:单个用户防刷
- resource: queryOrder
paramFlowItem:
- paramIdx: 0
count: 5 # 同一用户 5 QPS十、Resilience4j 备选方案
xml
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>yaml
resilience4j:
circuitbreaker:
instances:
paymentService:
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 5
ratelimiter:
instances:
createOrder:
limitForPeriod: 100
limitRefreshPeriod: 1s
timeoutDuration: 0
retry:
instances:
paymentService:
maxAttempts: 3
waitDuration: 1sjava
@Service
public class PaymentService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
@Retry(name = "paymentService")
@RateLimiter(name = "createOrder")
public void pay(OrderDTO dto) {
// 调用支付服务
}
public void fallback(OrderDTO dto, Throwable t) {
log.warn("支付服务降级", t);
// 排队、降级、补偿
}
}十一、本章小结
| 要点 | 关键 |
|---|---|
| 算法 | 计数器 / 滑动窗口 / 令牌桶(推荐)/ 漏桶 |
| 限流 | 拒掉过多请求(QPS / 线程数 / 热点参数) |
| 熔断 | 下游故障时快速失败,避免雪崩 |
| 降级 | 放弃非核心功能保核心 |
| 工具 | Sentinel(阿里)/ Resilience4j(Netflix) |
| 维度 | 网关层 / 服务层 / 方法层(多维度组合) |
| 状态机 | 关闭 → 打开 → 半开 → 关闭 |
动手练习
练习 1:基础题
集成 Sentinel,给订单创建接口加 QPS=10 的限流,用 JMeter 压测验证 11 QPS 时被限流。
练习 2:进阶题
实现一个用户维度的热点限流:同一用户 1 秒内最多 5 次请求,超过则拒绝。模拟某个用户用脚本刷接口,验证被拦截。
练习 3:思考题
你的秒杀系统有 3 层:网关、服务、方法。如何分层限流?分别用什么阈值?
下一章:第 70 章:定时任务与异步任务 →