刚刚接触学习限流这个概念知识, 经过一段时间的学习实践, 分享一下自己的心得
一、什么是限流?
1.1 限流的定义
限流(Rate Limiting) 是一种控制系统接收请求速率的技术。就像高速公路的收费站控制车辆进入速度一样,限流控制服务器处理请求的速度,防止服务器被过多请求压垮。
1.2 为什么需要限流?
想象一个场景:
- 你的网站正常情况下每秒处理 100 个请求
- 突然有人用脚本每秒发送 10000 个请求
- 服务器资源被耗尽,正常用户也无法访问
限流的作用:
- 保护服务器 - 防止资源耗尽(CPU、内存、数据库连接等)
- 保证服务质量 - 确保正常用户的请求能被处理(避免大量请求访问导致服务器崩溃)
- 防止恶意攻击 - 阻止 DDoS 攻击、暴力破解等,保护服务器
- 控制成本 - 避免因突发流量产生高额费用
1.3 生活中的限流例子
| 场景 | 限流方式 | 说明 |
|---|---|---|
| 游乐场 | 排队 + 限流 | 控制同时游玩人数 |
| 医院挂号 | 每日限额 | 每天只挂 500 个号 |
| 抢购活动 | 排队系统 | 防止服务器崩溃 |
| API 接口 | QPS 限制 | 控制每秒请求数 |
二、专有名词与限流算法解释
2.1 QPS(Queries Per Second)
QPS = 每秒查询数,表示服务器每秒能处理的请求数量。
示例:
如果 QPS 限制为 10,意味着:
- 每秒最多处理 10 个请求
- 第 11 个请求会被拒绝(限流)
时间轴:
第1秒: [请求1] [请求2] [请求3] ... [请求10] → 全部处理
第1秒: [请求1] [请求2] ... [请求10] [请求11] → 请求11被拒绝
相关概念:
- TPS(Transactions Per Second):每秒事务数,通常指数据库操作
- RPS(Requests Per Second):每秒请求数,和 QPS 类似
- 吞吐量(Throughput):单位时间内处理的请求数量
2.2 固定窗口(Fixed Window)
固定窗口 是一种限流算法,将时间划分为固定的窗口(如 1 秒),在每个窗口内计数请求数。
时间被划分为固定的 1 秒窗口:
|<---- 窗口1 (0-1秒) ---->|<---- 窗口2 (1-2秒) ---->|<---- 窗口3 (2-3秒) ---->|
| 请求: 1,2,3,4,5,6,7,8 | 请求: 1,2,3,4,5 | 请求: 1,2,3,4,5,6,7,8,9 |
| 计数: 8 (未超限) | 计数: 5 (未超限) | 计数: 9 (未超限) |
限流阈值: 10 QPS
优点:
- 实现简单
- 性能好
缺点:
-
窗口边界问题:如果在窗口结束前突然涌入大量请求,可能瞬间超过阈值
窗口边界问题示例:
|<---- 窗口1 ---->|<---- 窗口2 ---->|
| 请求: 9个 | 请求: 9个 |
| (最后0.1秒) | (最前0.1秒) |实际在 0.2 秒内处理了 18 个请求,远超 10 QPS 的限制!
2.3 滑动窗口(Sliding Window)
滑动窗口 是固定窗口的改进版,窗口随时间滑动,更平滑。
滑动窗口示例(1秒窗口,每100ms滑动一次):
时间: 0ms 100ms 200ms 300ms 400ms 500ms
窗口: [===] [===] [===] [===] [===] [===]
↓ ↓ ↓ ↓ ↓ ↓
滑动 滑动 滑动 滑动 滑动 滑动
每个时刻都统计过去 1 秒内的请求数
优点:
- 更平滑,避免边界问题
- 限流更精确
缺点:
- 实现复杂
- 内存消耗大(需要记录每个请求的时间戳)
2.4 漏桶算法(Leaky Bucket)
漏桶算法 就像一个漏桶,请求像水一样倒入桶中,以固定速率流出处理。
漏桶算法示意图:
请求进入
↓
┌───────┐
│ █████ │ ← 桶(队列)
│ █████ │
│ █████ │
└───┬───┘
↓
固定速率流出(如每秒 10 个)
↓
处理请求
工作原理:
- 请求进入桶(队列)
- 桶满则拒绝新请求
- 以固定速率从桶中取出请求处理
优点:
- 严格限制处理速率
- 平滑输出
缺点:
- 无法应对突发流量
- 即使服务器空闲,也只能按固定速率处理
2.5 令牌桶算法(Token Bucket)
令牌桶算法 以固定速率生成令牌,请求需要获取令牌才能被处理。
令牌桶算法示意图:
令牌生成器(每秒 10 个令牌)
↓
┌───────┐
│ ○○○○○ │ ← 令牌桶(存放令牌)
│ ○○○○○ │
└───────┘
↓
请求到达 → 获取令牌 → 处理请求
↓
没有令牌 → 拒绝请求
工作原理:
- 令牌以固定速率放入桶中
- 桶满则丢弃多余令牌
- 请求到达时,需要从桶中取一个令牌
- 有令牌则处理,没令牌则拒绝
优点:
- 允许一定程度的突发流量(桶中有积累的令牌)
- 实现相对简单
缺点:
- 需要定时器生成令牌
2.6 Redis INCR 命令
Redis 是一个高性能的内存数据库,INCR 是 Redis 的原子自增命令。
bash
# Redis INCR 命令示例
127.0.0.1:6379> SET counter 0
OK
127.0.0.1:6379> INCR counter
(integer) 1
127.0.0.1:6379> INCR counter
(integer) 2
127.0.0.1:6379> INCR counter
(integer) 3
原子性 意味着这个操作是不可分割的,多个请求同时执行 INCR 也不会出错。
在限流中的应用:
java
// 伪代码
Long count = redis.incr("rate_limiter:ip:192.168.1.1");
if (count == 1) {
redis.expire("rate_limiter:ip:192.168.1.1", 1); // 1秒后过期
}
if (count > 10) {
return "限流"; // 超过 10 QPS
}
上述代码就是利用Redis实现了1秒只处理10个请求的逻辑
2.7 TTL(Time To Live)
TTL = 生存时间,表示数据在缓存中的存活时间。
TTL 示例:
Key: rate_limiter:ip:192.168.1.1
Value: 5
TTL: 1 秒
1 秒后:
Key 自动删除(过期)
下次访问重新计数
2.8 过滤器(Filter)
过滤器 是 Web 应用中的一种组件,可以在请求到达控制器之前进行处理。
请求处理流程:
客户端请求
↓
┌─────────────┐
│ 过滤器1 │ ← 限流过滤器
├─────────────┤
│ 过滤器2 │ ← 验证码过滤器
├─────────────┤
│ 过滤器3 │ ← 认证过滤器
├─────────────┤
│ Controller │ ← 业务逻辑
└─────────────┘
↓
响应返回客户端
2.9 Spring Security 过滤器链
Spring Security 是 Spring 框架的安全模块,使用过滤器链实现安全控制。
Spring Security 过滤器链(简化版):
1. SecurityContextPersistenceFilter ← 保存安全上下文
2. RateLimiterFilter ← 限流
3. CaptchaValidationFilter ← 验证码验证
4. TokenAuthenticationFilter ← Token 认证
5. ExceptionTranslationFilter ← 异常处理
6. FilterSecurityInterceptor ← 权限检查
2.10 分布式锁(Distributed Lock)
分布式锁 是在分布式系统中实现互斥访问的机制。
分布式锁示例(使用 Redis SETNX):
请求A: SETNX lock:key value → 成功(获取锁)
请求B: SETNX lock:key value → 失败(锁已被占用)
请求A 处理完毕 → 删除锁
请求C: SETNX lock:key value → 成功(获取锁)
SETNX = SET if Not eXists,只有 key 不存在时才设置成功。
三、横向技术拓展
3.1 服务治理三剑客:限流、熔断、降级
┌─────────────────────────────────────────────────────────────────┐
│ 服务治理三剑客 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 限流 │ │ 熔断 │ │ 降级 │ │
│ │ Rate Limit │ │ Circuit │ │ Fallback │ │
│ │ │ │ Breaker │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ↓ ↓ ↓ │
│ 控制请求速率 快速失败 返回兜底响应 │
│ 保护服务器 防止级联故障 保证核心功能 │
│ │
└─────────────────────────────────────────────────────────────────┘
3.1.1 限流(Rate Limiting)
作用: 控制请求速率,防止服务器过载
正常情况:
客户端 → [限流] → 服务器 → 响应
超过限制:
客户端 → [限流] → 拒绝请求 → 返回 429 Too Many Requests
3.1.2 熔断(Circuit Breaker)
作用: 当依赖服务出现故障时,快速失败,防止级联故障
熔断器状态机:
┌─────────┐
│ 关闭 │ ← 正常状态,请求正常通过
│ (Closed) │
└────┬────┘
│ 失败次数超过阈值
↓
┌─────────┐
│ 打开 │ ← 故障状态,请求直接失败
│ (Open) │
└────┬────┘
│ 超时后进入半开状态
↓
┌─────────┐
│ 半开 │ ← 尝试恢复,允许部分请求通过
│ (Half- │
│ Open) │
└────┬────┘
│ 请求成功 → 回到关闭状态
│ 请求失败 → 回到打开状态
常用框架: Hystrix、Resilience4j、Sentinel
3.1.3 降级(Fallback)
作用: 当系统压力过大时,暂时关闭非核心功能,保证核心功能可用
降级策略示例:
功能优先级:
1. 核心功能(必须保证):下单、支付
2. 重要功能(尽量保证):查询订单、查看商品
3. 非核心功能(可降级):推荐系统、评论系统
降级时:
- 核心功能:正常处理
- 重要功能:正常处理
- 非核心功能:返回默认值或缓存数据
3.2 API 网关限流
API 网关 是微服务架构中的统一入口,可以在网关层实现限流。
微服务架构中的限流层次:
┌─────────────────────────────────────────────────────────────────┐
│ 客户端 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ API 网关(如 Spring Cloud Gateway、Kong) │
│ ├─ 全局限流:保护整个系统 │
│ ├─ 路由限流:针对特定服务 │
│ └─ 用户限流:针对特定用户 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 微服务 A 微服务 B 微服务 C 微服务 D │
│ (限流) (限流) (限流) (限流) │
└─────────────────────────────────────────────────────────────────┘
常见 API 网关:
- Spring Cloud Gateway:Spring 官方网关,支持限流
- Kong:基于 Nginx 的高性能网关
- Nginx:可通过 Lua 脚本实现限流
- APISIX:Apache 开源的高性能网关
3.3 分布式限流
在分布式系统中,单机限流无法保证全局一致性,需要使用分布式限流。
单机限流 vs 分布式限流:
单机限流:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 服务器1 │ │ 服务器2 │ │ 服务器3 │
│ 限流: 10 QPS│ │ 限流: 10 QPS│ │ 限流: 10 QPS│
└─────────────┘ └─────────────┘ └─────────────┘
总计: 30 QPS(不准确)
分布式限流:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 服务器1 │ │ 服务器2 │ │ 服务器3 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
↓
┌─────────────────┐
│ Redis 集群 │
│ 全局限流: 10 QPS│
└─────────────────┘
分布式限流实现方式:
- Redis + Lua 脚本(推荐)
lua
-- Lua 脚本示例(原子操作)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or "0")
if current + 1 > limit then
return 0 -- 超限
else
redis.call("INCR", key)
if current == 0 then
redis.call("EXPIRE", key, 1)
end
return 1 -- 未超限
end
- Redisson RateLimiter
java
// 使用 Redisson 实现分布式限流
RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter");
rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
// 处理请求
} else {
// 拒绝请求
}
- Sentinel
java
// 使用 Sentinel 实现限流
FlowRule rule = new FlowRule();
rule.setResource("myResource");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(10);
FlowRuleManager.loadRules(Collections.singletonList(rule));
3.4 限流算法对比
| 算法 | 实现复杂度 | 内存消耗 | 突发流量处理 | 精确度 | 适用场景 |
|---|---|---|---|---|---|
| 固定窗口 | ⭐ | ⭐ | ❌ | ⭐⭐ | 简单限流 |
| 滑动窗口 | ⭐⭐⭐ | ⭐⭐⭐ | ❌ | ⭐⭐⭐⭐ | 精确限流 |
| 漏桶算法 | ⭐⭐ | ⭐⭐ | ❌ | ⭐⭐⭐ | 流量整形 |
| 令牌桶算法 | ⭐⭐ | ⭐⭐ | ✅ | ⭐⭐⭐ | 允许突发 |
3.5 常见限流框架对比
| 框架 | 语言 | 特点 | 适用场景 |
|---|---|---|---|
| Sentinel | Java | 功能全面,阿里开源 | 微服务架构 |
| Resilience4j | Java | 轻量级,函数式编程 | Spring Boot |
| Guava RateLimiter | Java | 简单易用,单机限流 | 单体应用 |
| Hystrix | Java | 已停止维护 | 遗留项目 |
| Redis + Lua | 任意 | 灵活,分布式支持 | 分布式系统 |