第 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-rulesjava
// 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: 10000java
// 基于注解 + 自定义 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: 5000java
// Token Server 统一分配
// 各节点向 Token Server 请求令牌本章小结
| 算法 | 特点 |
|---|---|
| 固定窗口 | 简单,边界突刺 |
| 滑动窗口 | 精确,内存开销 |
| 令牌桶 | 允许突发 |
| 漏桶 | 削峰填谷 |
| 工具 | 特点 |
|---|---|
| Sentinel | 阿里,功能全 |
| Resilience4j | Spring 官方 |
| Guava RateLimiter | 简单,单机 |
| 策略 | 用途 |
|---|---|
| QPS | 入口流控 |
| 并发线程 | 慢服务保护 |
| 热点参数 | 秒杀 |
| 慢调用 | 降级兜底 |
动手练习
- 令牌桶:用代码实现令牌桶,模拟突发流量
- Sentinel 集成:在订单接口加限流 100 QPS,触发后返回兜底
- 热点限流:秒杀接口按 skuId 限流,每个商品 1000 QPS
- 降级演示:故意让 user 服务慢调用,触发熔断,观察 fallback
下一章:第 27 章:熔断与隔离 →