用 flask-limiter 结合 Redis 实现请求限流,配置 storage_uri 并注意 key_func 动态性;熔断需用 Redis Hash 自建状态机,避免本地存储;Key 要加前缀、转义、分 DB;连接池和 Lua 原子脚本保障高并发可靠性。

Flask 里怎么用 Redis 做请求限流
直接用 flask-limiter,别自己手写计数器逻辑。它底层默认就支持 Redis,配置简单、原子性有保障,自己拼 INCR + EXPIRE 容易漏掉竞态或过期时间错配。
常见错误是把 key_func 写成固定字符串,结果所有用户共享一个计数器;或者没配 storage_uri,退化成内存限流,多进程下完全失效。
- 安装:
pip install Flask-Limiter - 初始化时指定 Redis 地址:
Limiter(app, key_func=get_remote_address, storage_uri="redis://localhost:6379") - 按用户 IP 限流(最常用):
@limiter.limit("100 per day"),注意get_remote_address在反向代理后要改用request.headers.get("X-Forwarded-For", request.remote_addr) - 想按用户 ID 限流?确保登录态已解析,
key_func返回session.get("user_id"),但得处理未登录情况(比如返回"anonymous:" + ip)
Redis 熔断器怎么和 Flask API 绑定
Flask 本身不提供熔断能力,得靠 pydantic 或 tenacity 配合手动判断,但更稳的方式是用 redis-py 自建状态机:用一个 hash 存服务名 → 状态/失败次数/最后失败时间,再配合定时任务或请求前置检查。
容易踩的坑是把熔断状态存在本地变量或线程局部存储里——多 worker 下各管各的,根本起不到保护后端的作用;另一个是没设超时重置,一旦触发熔断就永远卡死。
- 推荐结构:
hset circuit_breaker:api_user_get {"state":"open","fail_count":5,"last_fail_ts":1712345678} - 每次调用前查
hget circuit_breaker:api_user_get state,如果是"open"直接返回 503 - 成功响应后用
hset circuit_breaker:api_user_get state "closed" fail_count 0;失败则递增fail_count并更新时间戳 - 加个后台任务(比如 APScheduler)每 30 秒扫一次,对
"open"且距上次失败超 60 秒的项设为"half-open"
限流 + 熔断组合时 Redis Key 设计要注意什么
Key 冲突是线上最隐蔽的问题之一。限流用的 flask-limiter 默认 key 是 "limit:{endpoint}:{key_func_result}",而你自己写的熔断器如果也用 "circuit:{endpoint}",看起来合理,但 endpoint 名可能含点号、斜杠,Redis key 里出现这些字符不会报错,却会导致 scan 或监控工具识别混乱。
更麻烦的是,两个模块都依赖同一个 Redis DB,但没做命名空间隔离,一不小心就互相覆盖 —— 比如熔断器清空 key 时误删了限流计数器。
- 统一加前缀:
limiter:api:v1:users:get:{ip}和circuit:api:v1:users:get - 避免动态部分含特殊字符:IP 地址里的点可以保留,但用户 ID 如果是邮箱,建议
hashlib.md5(email.encode()).hexdigest()[:8]截取 - 限流和熔断用不同 Redis DB(比如 DB 0 和 DB 1),比共用 DB + 前缀更可靠
- 所有 key 必须带 TTL,哪怕只是
EXPIRE key 86400,防止异常堆积占满内存
高并发下 Redis 连接和性能怎么扛住
单个 redis-py 连接在 Flask 多线程模式下会成为瓶颈,flask-limiter 默认用的是连接池,但池大小默认是 10,QPS 上千时大量请求卡在获取连接上,现象是响应延迟陡增、Redis client 超时错误变多,错误信息像 ConnectionError: Error 110 connecting to localhost:6379. Connection timed out.
另一个问题是 Lua 脚本没做原子合并,比如限流要先 INCR 再 EXPIRE,网络抖动时可能只执行了一半,导致 key 永久存在或没过期。
- 调大连接池:
storage_options={"connection_pool_size": 50}(根据压测调整,别盲目设 1000) - 所有涉及多命令的逻辑必须封装进 Lua 脚本,例如限流的“增+设过期”写在一个脚本里,用
redis.eval(script, 0, key, expire) - 别在请求中频繁
redis.ping()健康检查,改用连接池的health_check_interval参数 - Redis 用
redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru,防 OOM
真正难的不是写通逻辑,而是让限流和熔断的状态在分布式、多进程、反向代理、网络分区各种现实条件下保持一致。Redis 不是银弹,它自己挂了怎么办?有没有 fallback 降级策略?这些细节没对齐,上线后第一波流量就会暴露出来。










