SlowAPI返回429不生效的根本原因是默认内存存储在多进程部署下各worker独立计数,导致限流失效;生产环境必须改用RedisStorage等共享存储,并正确初始化Limiter、挂载中间件、规范速率字符串格式(如“10 per minute”),同时启用fail_safe=True避免Redis故障引发500错误。

为什么直接用 slowapi 会返回 429 却不生效?
常见现象是:配置完 Limter 和 limit 装饰器,接口仍能高频调用,429 Too Many Requests 响应压根不出现。根本原因是 slowapi 默认使用内存存储(MemoryStorage),而 FastAPI 多进程部署(如 uvicorn --workers 4)会导致各 worker 进程维护独立计数器,限流完全失效。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 生产环境必须显式替换为共享存储,如
RedisStorage(推荐)或MemcachedStorage - 若仅本地调试,可先用
MemoryStorage验证逻辑,但需加注释提醒不可用于部署 - 检查
uvicorn启动参数:避免无意识启用多 worker,比如--workers 1是安全底线
如何正确初始化 SlowAPILimiter 并挂载到 FastAPI 实例?
不能在路由函数里临时创建 Limiter,否则中间件无法拦截请求;也不能漏掉 app.state.limiter 赋值,否则装饰器找不到实例。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在主应用文件(如
main.py)顶部初始化Limiter,例如:from slowapi import Limiter from slowapi.util import get_remote_address from slowapi.middleware import SlowAPIMiddleware limiter = Limiter(key_func=get_remote_address, storage_uri="redis://localhost:6379")
- 立即挂载中间件:
app = FastAPI() app.state.limiter = limiter app.add_middleware(SlowAPIMiddleware)
- 确保
get_remote_address在反向代理后能取到真实 IP:若用 Nginx,需配置proxy_set_header X-Real-IP $remote_addr;并改用get_real_ip(需自定义 key_func)
@limiter.limit 的速率字符串怎么写才不踩坑?
格式是 "<number><unit> per <interval>"</interval></unit></number>,但单位拼写、空格、复数形式都敏感。写成 "10/minute" 或 "10 per minute" 都会解析失败,静默退化为不限流。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 严格按文档用空格分隔,单位用单数:
"10 per minute"✅,"10 per minutes"❌ - 支持的单位只有:
second、minute、hour、day - 多个限流策略可用分号分隔:
"5 per minute; 100 per day",触发任一即返回 429 - 对特定路径动态限流?用 lambda 替换
key_func,例如按用户 ID:@limiter.limit("100 per day", key_func=lambda request: request.state.user_id)
为什么 Redis 连接失败时 API 直接报 500 而不是降级?
slowapi 默认不处理存储异常,Redis 不可用时抛出 ConnectionError,FastAPI 捕获后返回 500。这违背“限流失败不应影响业务”的原则。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 初始化
Limiter时传入fail_safe=True参数,让存储异常时跳过限流(相当于不限制) - 配合健康检查暴露存储状态,例如加一个
/health接口调用limiter._storage.check() - 注意:
fail_safe不影响内存模式,只对 Redis/Memcached 生效
最易被忽略的是:限流规则作用域默认是全局路径,如果某个接口要单独限流,必须显式在装饰器里指定 key_func 或用 group 分组,否则所有路由共用同一计数器。


















