文章目录
-
- [一、Sentinel 限流算法概述](#一、Sentinel 限流算法概述)
- [二、滑动窗口算法(Sliding Window)](#二、滑动窗口算法(Sliding Window))
-
- [1. 算法原理](#1. 算法原理)
- [2. 实现机制](#2. 实现机制)
- [3. 特点](#3. 特点)
- [4. 代码示例](#4. 代码示例)
- [三、令牌桶算法(Token Bucket)](#三、令牌桶算法(Token Bucket))
-
- [1. 算法原理](#1. 算法原理)
- [2. 实现机制](#2. 实现机制)
- [3. 特点](#3. 特点)
- [4. 代码示例](#4. 代码示例)
- [四、漏桶算法(Leaky Bucket)](#四、漏桶算法(Leaky Bucket))
-
- [1. 算法原理](#1. 算法原理)
- [2. 实现机制](#2. 实现机制)
- [3. 特点](#3. 特点)
- [4. 代码示例](#4. 代码示例)
- [五、预热限流算法(Warm Up)](#五、预热限流算法(Warm Up))
-
- [1. 算法原理](#1. 算法原理)
- [2. 实现机制](#2. 实现机制)
- [3. 特点](#3. 特点)
- [4. 代码示例](#4. 代码示例)
- [六、排队等待算法(Rate Limiter)](#六、排队等待算法(Rate Limiter))
-
- [1. 算法原理](#1. 算法原理)
- [2. 实现机制](#2. 实现机制)
- [3. 特点](#3. 特点)
- [4. 代码示例](#4. 代码示例)
- [七、Sentinel 中的实际应用](#七、Sentinel 中的实际应用)
-
- [1. 流控规则配置](#1. 流控规则配置)
- [2. 代码中使用](#2. 代码中使用)
- 八、算法对比
- 九、常见问题
-
- [Q1: Sentinel 默认使用什么算法?](#Q1: Sentinel 默认使用什么算法?)
- [Q2: 令牌桶和漏桶的区别?](#Q2: 令牌桶和漏桶的区别?)
- [Q3: 如何选择限流算法?](#Q3: 如何选择限流算法?)
- [Q4: 预热限流的作用?](#Q4: 预热限流的作用?)
- 十、总结
一、Sentinel 限流算法概述
Sentinel 提供了多种限流算法,主要包括:
- 滑动窗口算法(默认)
- 令牌桶算法
- 漏桶算法
- 预热限流算法
- 排队等待算法
二、滑动窗口算法(Sliding Window)
1. 算法原理
滑动窗口是 Sentinel 的默认限流算法,通过时间窗口内的请求数来控制流量。
2. 实现机制
时间轴:0s 1s 2s 3s 4s 5s
窗口1: [====]
窗口2: [====]
窗口3: [====]
窗口4: [====]
每个窗口统计该时间段的请求数
3. 特点
- 时间窗口:将时间分成多个固定大小的窗口(如 1 秒)
- 请求计数:每个窗口内统计请求数量
- 滑动计算:当前窗口的请求数超过阈值时触发限流
- 精确控制:可以精确控制每秒的请求数
4. 代码示例
java
// Sentinel 滑动窗口实现(简化版)
public class SlidingWindow {
private final int windowSize; // 窗口大小(秒)
private final int threshold; // 阈值(每秒请求数)
private final AtomicInteger[] counters; // 每个窗口的计数器
public boolean tryAcquire() {
long currentTime = System.currentTimeMillis() / 1000;
int windowIndex = (int) (currentTime % windowSize);
// 获取当前窗口的计数器
AtomicInteger counter = counters[windowIndex];
// 检查是否超过阈值
if (counter.get() >= threshold) {
return false; // 限流
}
// 增加计数
counter.incrementAndGet();
return true;
}
}
三、令牌桶算法(Token Bucket)
1. 算法原理
令牌桶以固定速率生成令牌,请求需要获取令牌才能通过。
2. 实现机制
令牌桶:
┌─────────────┐
│ 令牌数量 │ ← 固定速率生成令牌(如每秒 10 个)
│ 10 │
│ │
│ 容量:20 │
└─────────────┘
↓
请求到达 → 消耗 1 个令牌 → 通过
→ 没有令牌 → 限流
3. 特点
- 固定速率:以固定速率生成令牌(如每秒 10 个)
- 桶容量:令牌桶有最大容量,超过容量后不再生成
- 突发流量:允许短时间内的突发流量(消耗积累的令牌)
- 平滑限流:长期平均速率受限于令牌生成速率
4. 代码示例
java
// 令牌桶算法实现(简化版)
public class TokenBucket {
private final int capacity; // 桶容量
private final int refillRate; // 令牌生成速率(每秒)
private volatile int tokens; // 当前令牌数
private volatile long lastRefillTime; // 上次补充令牌的时间
public synchronized boolean tryAcquire() {
// 1. 补充令牌
refillTokens();
// 2. 检查是否有令牌
if (tokens > 0) {
tokens--;
return true;
}
return false; // 没有令牌,限流
}
private void refillTokens() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastRefillTime;
// 计算应该补充的令牌数
int tokensToAdd = (int) (elapsedTime * refillRate / 1000);
if (tokensToAdd > 0) {
tokens = Math.min(capacity, tokens + tokensToAdd);
lastRefillTime = currentTime;
}
}
}
四、漏桶算法(Leaky Bucket)
1. 算法原理
漏桶以固定速率处理请求,超出容量的请求会被丢弃或排队。
2. 实现机制
漏桶:
请求 → ┌─────────────┐ → 固定速率流出(如每秒 5 个)
│ 请求队列 │
│ 10 │
│ 容量:20 │
└─────────────┘
↓
固定速率处理
3. 特点
- 固定速率:以固定速率处理请求
- 桶容量:漏桶有最大容量,超过容量后请求被丢弃
- 平滑输出:输出速率恒定,不会出现突发
- 适合场景:需要严格控制输出速率的场景
4. 代码示例
java
// 漏桶算法实现(简化版)
public class LeakyBucket {
private final int capacity; // 桶容量
private final int leakRate; // 漏出速率(每秒)
private volatile int requests; // 当前请求数
private volatile long lastLeakTime; // 上次漏出的时间
public synchronized boolean tryAcquire() {
// 1. 漏出请求
leakRequests();
// 2. 检查是否有容量
if (requests < capacity) {
requests++;
return true;
}
return false; // 桶满了,限流
}
private void leakRequests() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastLeakTime;
// 计算应该漏出的请求数
int requestsToLeak = (int) (elapsedTime * leakRate / 1000);
if (requestsToLeak > 0) {
requests = Math.max(0, requests - requestsToLeak);
lastLeakTime = currentTime;
}
}
}
五、预热限流算法(Warm Up)
1. 算法原理
预热限流在系统启动时逐步提高限流阈值,避免冷启动时的高并发冲击。
2. 实现机制
时间轴:0s 5s 10s 15s 20s
阈值: 100 → 200 → 300 → 400 → 500(最终阈值)
逐步提高限流阈值,让系统逐步适应流量
3. 特点
- 逐步提高:限流阈值从低到高逐步提高
- 预热时间:在预热时间内逐步达到最终阈值
- 保护系统:避免系统启动时的高并发冲击
- 适合场景:系统冷启动、长时间低流量后突然高流量
4. 代码示例
java
// 预热限流算法实现(简化版)
public class WarmUpRateLimiter {
private final int finalThreshold; // 最终阈值
private final long warmUpPeriod; // 预热时间(毫秒)
private final long startTime; // 启动时间
public int getCurrentThreshold() {
long elapsedTime = System.currentTimeMillis() - startTime;
if (elapsedTime >= warmUpPeriod) {
// 预热完成,使用最终阈值
return finalThreshold;
}
// 预热阶段,逐步提高阈值
double factor = (double) elapsedTime / warmUpPeriod;
return (int) (finalThreshold * factor);
}
}
六、排队等待算法(Rate Limiter)
1. 算法原理
排队等待允许请求排队等待,而不是直接拒绝,适合需要保证请求不丢失的场景。
2. 实现机制
请求队列:
请求1 → ┌─────────────┐ → 处理
请求2 → │ 等待队列 │ → 处理
请求3 → │ 5 │ → 处理
请求4 → │ 容量:10 │ → 处理
请求5 → └─────────────┘ → 处理
3. 特点
- 排队等待:请求可以排队等待,而不是直接拒绝
- 队列容量:队列有最大容量,超过容量后请求被拒绝
- 保证处理:适合需要保证请求不丢失的场景
- 延迟增加:请求可能需要等待,响应时间增加
4. 代码示例
java
// 排队等待算法实现(简化版)
public class RateLimiter {
private final int maxQueueSize; // 最大队列容量
private final BlockingQueue<Request> queue; // 请求队列
private final int processRate; // 处理速率(每秒)
public boolean tryAcquire(Request request) {
// 尝试加入队列
if (queue.size() < maxQueueSize) {
queue.offer(request);
return true;
}
return false; // 队列满了,拒绝
}
// 后台线程处理队列中的请求
private void processQueue() {
while (true) {
try {
Request request = queue.take();
processRequest(request);
Thread.sleep(1000 / processRate); // 控制处理速率
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
七、Sentinel 中的实际应用
1. 流控规则配置
java
// Sentinel 流控规则
FlowRule rule = new FlowRule();
rule.setResource("userService"); // 资源名称
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流维度:QPS
rule.setCount(100); // 阈值:每秒 100 个请求
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 默认:直接拒绝
// 预热限流
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
rule.setWarmUpPeriodSec(10); // 预热时间:10 秒
// 排队等待
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
rule.setMaxQueueingTimeMs(500); // 最大等待时间:500 毫秒
2. 代码中使用
java
@Service
public class UserService {
@SentinelResource(
value = "userService",
blockHandler = "handleBlock"
)
public User getUser(Long id) {
return userRepository.findById(id);
}
// 限流降级方法
public User handleBlock(Long id, BlockException e) {
return new User(); // 返回默认值
}
}
八、算法对比
| 算法 | 特点 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 滑动窗口 | 固定时间窗口内限制请求数 | 精确控制 QPS | 实现简单,精确控制 | 窗口边界可能突发 |
| 令牌桶 | 固定速率生成令牌 | 允许突发流量 | 允许突发,平滑限流 | 实现较复杂 |
| 漏桶 | 固定速率处理请求 | 严格控制输出速率 | 输出速率恒定 | 不允许突发 |
| 预热限流 | 逐步提高阈值 | 系统冷启动 | 保护系统 | 预热期间限流较严格 |
| 排队等待 | 请求排队等待 | 保证请求不丢失 | 不丢失请求 | 响应时间增加 |
九、常见问题
Q1: Sentinel 默认使用什么算法?
A : 滑动窗口算法,通过时间窗口内的请求数来控制流量。
Q2: 令牌桶和漏桶的区别?
A:
- 令牌桶:固定速率生成令牌,允许突发流量
- 漏桶:固定速率处理请求,不允许突发
Q3: 如何选择限流算法?
A:
- 精确控制 QPS:滑动窗口
- 允许突发流量:令牌桶
- 严格控制输出:漏桶
- 系统冷启动:预热限流
- 保证不丢失:排队等待
Q4: 预热限流的作用?
A: 在系统启动时逐步提高限流阈值,避免冷启动时的高并发冲击,保护系统稳定运行。
十、总结
核心算法
- 滑动窗口:Sentinel 默认算法,精确控制 QPS
- 令牌桶:允许突发流量,适合流量波动大的场景
- 漏桶:严格控制输出速率,适合需要平滑输出的场景
- 预热限流:保护系统冷启动
- 排队等待:保证请求不丢失
选择建议
- 一般场景:使用滑动窗口(默认)
- 允许突发:使用令牌桶
- 严格控制:使用漏桶
- 冷启动:使用预热限流
- 保证不丢失:使用排队等待