Skip to content

Commit 34952ba

Browse files
committed
docs describe rate limit backend strategy
1 parent 9c01543 commit 34952ba

2 files changed

Lines changed: 256 additions & 0 deletions

File tree

docs/framework-starter-usage.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ spring:
146146
keystone:
147147
file-base-dir: ./data
148148
rsaPrivateKey: ${KEYSTONE_RSA_PRIVATE_KEY:}
149+
rate-limit:
150+
backend: redis
151+
fallback-to-local: false
149152
```
150153
151154
## 数据库迁移
@@ -170,6 +173,21 @@ spring:
170173
- classpath:db/migrate/app/mysql
171174
```
172175
176+
## 限流配置
177+
178+
框架接口通过 `@RateLimit` 声明限流规则。注解不选择 Redis 或本地内存,后端由全局配置控制:
179+
180+
```yaml
181+
keystone:
182+
rate-limit:
183+
backend: redis
184+
fallback-to-local: false
185+
```
186+
187+
`backend=redis` 是生产默认,适合多实例部署。`backend=local` 只建议用于本地开发或单实例场景。`fallback-to-local=false` 是生产推荐值,避免 Redis 故障时全局限流静默退化为每节点限流。
188+
189+
完整设计见 [Keystone 限流设计](rate-limit-design.md)。
190+
173191
## 自动配置开关
174192

175193
默认全部启用:

docs/rate-limit-design.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# Keystone 限流设计
2+
3+
## 1. 背景
4+
5+
Keystone 的限流能力通过 `@RateLimit` 注解应用在 Controller 方法上。早期设计允许每个注解自行选择 Redis 或本地 Map 作为限流存储,这会把业务规则和运行时基础设施选择混在一起:
6+
7+
- 接口作者需要理解部署形态,才能决定 `cacheType`
8+
- 多节点生产环境中误用本地内存会削弱全局限流。
9+
- Redis 和本地实现必须长期保持完全一致,否则同一个注解会因为存储类型不同而表现不同。
10+
- Redis 故障时是否降级属于系统运行策略,不应由单个接口注解决定。
11+
12+
新的设计将注解收敛为纯业务规则,后端实现由全局配置统一控制。
13+
14+
## 2. 设计目标
15+
16+
1. `@RateLimit` 只描述限流规则,不暴露 Redis/本地内存等实现选择。
17+
2. 生产默认使用 Redis,保证多实例部署时限流计数共享。
18+
3. Redis 不可用时默认失败,不静默降级,避免集群限流被悄悄放大。
19+
4. 是否降级到本地内存必须通过全局配置显式开启。
20+
5. Redis 和本地内存实现保持相同的固定窗口语义。
21+
6. 限流 key 生成集中管理,避免不同实现生成不同 key。
22+
23+
## 3. 非目标
24+
25+
| 非目标 | 说明 |
26+
| --- | --- |
27+
| 滑动窗口限流 | 当前实现是固定窗口计数,满足登录、验证码、公钥等接口保护需求 |
28+
| 分布式降级一致性 | 本地降级只保证单 JVM 内有效,不提供集群一致性 |
29+
| 每个接口选择后端 | 后端选择属于部署策略,不属于接口规则 |
30+
| 自动探测 Redis 健康并静默降级 | Redis 故障默认应暴露为错误,防止生产限流弱化 |
31+
32+
## 4. 使用方式
33+
34+
接口只声明规则:
35+
36+
```java
37+
@RateLimit(key = RateLimitKey.LOGIN_CAPTCHA_KEY, time = 10, maxCount = 10, limitType = LimitType.IP)
38+
@GetMapping("/captchaImage")
39+
public ResponseDTO<CaptchaDTO> getCaptchaImg() {
40+
...
41+
}
42+
```
43+
44+
配置后端策略:
45+
46+
```yaml
47+
keystone:
48+
rate-limit:
49+
backend: redis
50+
fallback-to-local: false
51+
```
52+
53+
| 配置项 | 默认值 | 说明 |
54+
| --- | --- | --- |
55+
| `keystone.rate-limit.backend` | `redis` | 限流存储后端,支持 `redis`、`local` |
56+
| `keystone.rate-limit.fallback-to-local` | `false` | Redis 限流缓存失败时是否降级到本地内存 |
57+
58+
环境变量:
59+
60+
```text
61+
KEYSTONE_RATE_LIMIT_BACKEND=redis
62+
KEYSTONE_RATE_LIMIT_FALLBACK_TO_LOCAL=false
63+
```
64+
65+
## 5. 模块结构
66+
67+
```text
68+
app.keystone.infrastructure.annotations.ratelimit
69+
RateLimit 限流规则注解
70+
RateLimitBackend 全局后端枚举
71+
RateLimitChecker 限流统一入口
72+
RateLimitKey 业务 key 常量
73+
RateLimitKeyGenerator 限流 key 生成器
74+
RateLimitProperties keystone.rate-limit 配置
75+
RateLimiterAspect AOP 切面
76+
77+
app.keystone.infrastructure.annotations.ratelimit.implementation
78+
AbstractRateLimitChecker 参数校验
79+
RedisRateLimitChecker Redis 固定窗口计数
80+
LocalRateLimitChecker 本地固定窗口计数
81+
```
82+
83+
## 6. 执行链路
84+
85+
```text
86+
Controller method
87+
-> RateLimiterAspect
88+
-> RateLimitChecker
89+
-> backend=local
90+
-> LocalRateLimitChecker
91+
-> backend=redis
92+
-> RedisRateLimitChecker
93+
-> Redis GET_CACHE_FAILED 且 fallback-to-local=true
94+
-> LocalRateLimitChecker
95+
```
96+
97+
`RateLimiterAspect` 不再知道 Redis 或本地内存的存在,只依赖统一入口 `RateLimitChecker`。
98+
99+
## 7. 限流 key 设计
100+
101+
所有实现都通过 `RateLimitKeyGenerator` 生成 key。
102+
103+
格式:
104+
105+
```text
106+
<base-key>:<limit-type>:<discriminator>
107+
```
108+
109+
示例:
110+
111+
```text
112+
Rate-Limit\:Login-Captcha:IP:10.0.0.1
113+
Rate-Limit\:Test:GLOBAL:GLOBAL
114+
Rate-Limit\:User:APP_USER:USER\:42
115+
```
116+
117+
规则:
118+
119+
1. `base-key` 为空时使用 `RateLimitKey.PREFIX`。
120+
2. `base-key` 末尾的 `:` 会被规范化移除。
121+
3. 每个片段都会转义 `:` 和 `\`,避免分隔符歧义。
122+
4. `GLOBAL` 使用固定判别值 `GLOBAL`。
123+
5. `IP` 从当前 Servlet request 中解析客户端 IP。
124+
6. `SYSTEM_USER` 和 `APP_USER` 要求当前 `Authentication` 已认证。
125+
7. 用户维度优先使用 `userId`,其次 `username`,最后 `cachedKey`。
126+
127+
## 8. 后端策略
128+
129+
### 8.1 Redis 后端
130+
131+
Redis 是默认后端,适合生产和多实例部署。
132+
133+
行为:
134+
135+
1. 使用 Lua 脚本保证计数和过期设置的原子性。
136+
2. 第一次访问时 `INCR` 并设置 `EXPIRE`。
137+
3. 窗口内当前计数超过 `maxCount` 时拒绝。
138+
4. Redis 调用失败或返回空值时抛出 `GET_CACHE_FAILED`。
139+
140+
### 8.2 本地后端
141+
142+
本地后端使用 JVM 内 Guava Cache 保存固定窗口计数,只适合:
143+
144+
- 本地开发。
145+
- 单实例部署。
146+
- Redis 故障时显式允许的临时降级。
147+
148+
限制:
149+
150+
- 多节点之间不共享计数。
151+
- 节点数越多,整体允许请求数可能按节点数放大。
152+
- 进程重启后计数丢失。
153+
154+
### 8.3 降级策略
155+
156+
默认:
157+
158+
```yaml
159+
fallback-to-local: false
160+
```
161+
162+
原因是生产环境中,Redis 故障后自动降级本地内存会把全局限流变成每节点限流。例如 4 个节点、`10 秒 10 次`,实际可能变成 `10 秒 40 次`。这类静默弱化比直接失败更难发现。
163+
164+
只有在明确接受该风险时才开启:
165+
166+
```yaml
167+
keystone:
168+
rate-limit:
169+
backend: redis
170+
fallback-to-local: true
171+
```
172+
173+
开启后,仅 Redis 缓存失败会降级。业务侧的超限异常不会降级。
174+
175+
## 9. 固定窗口语义
176+
177+
Redis 和本地内存都使用固定窗口:
178+
179+
```text
180+
time = 10
181+
maxCount = 5
182+
```
183+
184+
表示同一个 key 在 10 秒窗口内最多允许 5 次。第 6 次抛出 `COMMON_REQUEST_TOO_OFTEN`。窗口过期后重新计数。
185+
186+
`time <= 0` 或 `maxCount <= 0` 是配置错误,会抛出 `INVALID_PARAMETER`。
187+
188+
## 10. 推荐配置
189+
190+
### 10.1 生产多实例
191+
192+
```yaml
193+
keystone:
194+
rate-limit:
195+
backend: redis
196+
fallback-to-local: false
197+
```
198+
199+
### 10.2 本地开发
200+
201+
```yaml
202+
keystone:
203+
rate-limit:
204+
backend: local
205+
```
206+
207+
### 10.3 单实例临时容错
208+
209+
```yaml
210+
keystone:
211+
rate-limit:
212+
backend: redis
213+
fallback-to-local: true
214+
```
215+
216+
仅建议用于低风险、单实例或明确可接受限流弱化的环境。
217+
218+
## 11. 测试要求
219+
220+
限流相关改动至少运行:
221+
222+
```powershell
223+
.\gradlew.bat :keystone-infrastructure:test --tests "app.keystone.infrastructure.annotations.*" --tests "app.keystone.infrastructure.annotations.ratelimit.*" --tests "app.keystone.infrastructure.annotations.ratelimit.implementation.*"
224+
```
225+
226+
基础设施模块完整验证:
227+
228+
```powershell
229+
.\gradlew.bat :keystone-infrastructure:test
230+
.\gradlew.bat :keystone-infrastructure:check
231+
```
232+
233+
当前测试覆盖:
234+
235+
- key 生成、转义、IP 上下文、用户认证边界。
236+
- Redis 固定窗口脚本调用、超限、缓存失败、参数校验。
237+
- 本地固定窗口计数、窗口过期、参数校验。
238+
- 统一 `RateLimitChecker` 的 Redis 默认、本地后端、默认不降级、显式降级、客户端超限不降级。

0 commit comments

Comments
 (0)