Files
cs-note/hzh/REDIS/多级限流架构.md
T

247 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [redis, rate-limiting, multi-layer, architecture]
create time: 2026-06-01 10:30
---
# 多级限流架构实战
## 概述
单个限流算法通常不够用。生产环境中的限流像洋葱一样**层层叠加**——每层保护不同的目标,使用不同的算法和维度。这种设计既防御了大规模攻击,又为正常用户提供了良好的配额管理。
对应薄弱点 F5:多级限流架构各层的设计选择。
## 一、为什么需要多级?
```mermaid
flowchart LR
subgraph "单一维度的局限"
L1["仅 IP 限流"] --> PROBLEM1["问题:无法区分同一 IP 下的不同用户"]
L2["仅用户限流"] --> PROBLEM2["问题:恶意攻击者可注册大量账号绕过"]
L3["仅接口限流"] --> PROBLEM3["问题:不防刷单/暴力扫描"]
end
PROBLEM1 --> MULTI["→ 需要多级组合"]
PROBLEM2 --> MULTI
PROBLEM3 --> MULTI
style PROBLEM1 fill:#ffebee
style PROBLEM2 fill:#ffebee
style PROBLEM3 fill:#ffebee
style MULTI fill:#e8f5e9
```
单层限流的盲区:
- 只看 IP → 一个用户的多个账号共享配额(或反之,一个 IP 下多个用户被误伤)
- 只看用户 → 恶意用户可以创建无数个账号突破限制
- 只看接口 → 对暴力破解、爬虫扫描无能为力
## 二、经典三级限流架构
```mermaid
flowchart TD
Client["👤 客户端请求"] --> GW["🌐 API Gateway"]
GW --> L1
subgraph L1Layer["🔴 L1: IP 维度 - 第一道防线"]
direction LR
L1Action["防暴力扫描<br/>防恶意攻击"]
L1Algorithm["短窗口固定计数<br/>例如: 100 req/s/IP"]
end
L1 -- pass --> L2
subgraph L2Layer["🟡 L2: 用户维度 - 配额管理"]
direction LR
L2Action["控制每日/每月配额<br/>区分免费/付费等级"]
L2Algorithm["天级滑动窗口日志<br/>例如: 1000 req/day"]
end
L2 -- pass --> L3
subgraph L3Layer["🟢 L3: 接口维度 - 保护后端"]
direction LR
L3Action["保护实例不被打满<br/>QPS/内存保护"]
L3Algorithm["令牌桶/漏桶<br/>例如: 5000 req/s/endpoint"]
end
L3 -- pass --> Biz["💼 执行业务逻辑"]
L1 -- fail --> R429["HTTP 429 Too Many Requests"]
L2 -- fail --> R429
L3 -- fail --> R429
style L1Layer fill:#ffebee,color:#000
style L2Layer fill:#fff3e0,color:#000
style L3Layer fill:#e8f5e9,color:#000
style R429 fill:#b71c1c,color:#fff
```
### 层级详解
| 层级 | 限流维度 | 核心目标 | 推荐算法 | 典型参数 | Key 示例 |
|------|---------|---------|---------|---------|---------|
| **L1** | IP / CIDR | 防刷、防攻击、防爬虫 | 短窗口固定计数 | 100 req/s/IP | `rate:ip:{IP}:short` |
| **L2** | 用户 ID / Account | 配额管理(免费/付费) | 天级滑动窗口日志 | 1000 req/day | `rate:user:{UID}:daily` |
| **L3** | Endpoint / Interface | 保护下游资源 | 令牌桶 / 漏桶 | 5000 req/s/path | `rate:api:{path}` |
### L1:IP 层 - 最短视的窗口
```
时间窗口:1 ~ 5 秒
算法:固定窗口计数器
目的:快速拦截自动化攻击
为什么用短窗口?
- 恶意流量的特点是短时间内密集发起
- 短窗口可以及时响应,不留攻击窗口
- 不会因为窗口太长导致误伤正常用户的突发流量
```
```go
// L1: IP 限流 - 短窗口固定计数
const shortWindowLua = `
local key = KEYS[1]
local maxCount = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local count = redis.call('INCR', key)
if count == 1 then
redis.call('EXPIRE', key, window)
end
return count
`
func IPLimitAllow(ctx context.Context, rdb *redis.Client, ip string) (bool, error) {
// 5 秒窗口,最多 100 次请求
const windowSec = 5
const maxReq = 100
key := fmt.Sprintf("rate:ip:{%s}", ip)
result, err := rdb.Eval(ctx, shortWindowLua, []string{key}, maxReq, windowSec).Int()
if err != nil {
return false, err
}
return result <= maxReq, nil
}
```
### L2:用户层 - 精确的日配额
```
时间窗口:24 小时(按天重置)
算法:Sorted Set 滑动窗口(逐条精确统计)
目的:区分免费用户和付费用户的配额
为什么用 Sorted Set?
- 日配额 QPS 不高(1000/day ≈ 0.01 QPS)
- 需要精确统计,不能有近似误差
- 免费/付费分级,每个用户独立配额
```
### L3:接口层 - 保护后端资源
```
时间窗口:持续监控(令牌桶模型)
算法:令牌桶
目的:防止某个接口被打爆,保护数据库/下游服务
为什么用令牌桶?
- O(1) 空间,不随时间增长
- 允许合理突发(用户批量操作)
- 输出速率可控,保护后端稳定
```
## 三、多级限流的调用链路
```mermaid
sequenceDiagram
participant C as 客户端
participant G as Go Service
participant R as Redis
C->>G: HTTP Request
G->>R: L1: IP Check (100/s/5s)
alt Blocked by L1
R-->>G: count > 100
G-->>C: 429 (L1 blocked)
else
G->>R: L2: User Check (1000/day)
alt Blocked by L2
R-->>G: count >= 1000
G-->>C: 429 (L2 blocked)
else
G->>R: L3: Token Bucket (5000/s)
alt Blocked by L3
R-->>G: no tokens
G-->>C: 429 (L3 blocked)
else
R-->>G: allowed
G->>G: Execute Business Logic
G-->>C: 200 OK
end
end
end
```
> [!note]- 性能考量
>
> 多级限流意味着每次请求要发 **3 次 Redis 调用**。优化方向:
> - **本地缓存 + 远程校验**:L1 可以在本地做短期计数(如 sync.Map),定期同步到 Redis
> - **Pipeline 合并**:L1/L2/L3 的三个 Lua 调用可以用 Pipeline 打包
> - **短路策略**:如果 L1 就拒绝了,不需要继续查 L2/L3
## 四、降级与熔断联动
```mermaid
flowchart TD
Normal["✅ 正常限流状态"] --> Check{"Redis 可用?"}
Check -- "是" --> RL["正常走多级限流"]
Check -- "否" --> Degradation{"降级策略"}
Degradation -- "短暂断开" --> Local["本地内存限流<br/>降级模式"]
Degradation -- "长时间不可用" --> PermitAll["放行所有请求<br/>宁可超限不可停机"]
Local --> Recovery{"Redis 恢复?"}
Recovery -- "是" --> Normal
Recovery -- "否" --> PermitAll
style Normal fill:#c8e6c9
style PermitAll fill:#fff3e0
style Local fill:#e3f2fd
```
| Redis 状态 | L1 IP 层 | L2 用户层 | L3 接口层 |
|-----------|---------|----------|----------|
| **正常** | Redis Lua | Redis Lua | Redis Lua |
| **短暂故障** | 本地 sync.Map 缓存计数 | 跳过(默认放行) | 本地令牌桶 |
| **长时间故障** | 全部放行 | 全部放行 | 全部放行 |
> [!warning]- 降级不是无限制的
>
> 如果 L1(IP 层)也降级放行了,意味着任何 IP 都没有限制——这时应该配合 CDN/WAF 等外部防御层做兜底。
## 五、各层的超时与重试配置
```
多层限流系统的超时设置:
┌─────────┬──────────────┬──────────────────────────┐
│ 层级 │ Redis 超时 │ 客户端超时 │
├─────────┼──────────────┼──────────────────────────┤
│ L1 │ 1ms │ 50ms(快速判断) │
│ L2 │ 5ms │ 200ms(配额查询) │
│ L3 │ 3ms │ 100ms(令牌检查) │
│ 合计 │ 9ms │ — │
└─────────┴──────────────┴──────────────────────────┘
```
各层超时应该远小于整体业务超时,否则限流模块本身会成为瓶颈。
## 关联笔记
- [[分布式限流]] — 限流的基础算法介绍
- [[Jitter抖动与重试策略]] — 各层的客户端重试策略设计
- [[02-服务治理/06-容错模式]] — 限流在容错体系中的位置