bigywater.tech
Flask · 76

Flask-Limiter 限流

给接口加限流,响应头里有什么?固定窗口和滑动窗口有什么区别?多 worker 部署时 memory 存储还有效吗?在代理后面按 IP 限流为什么会误伤?这篇用真实的 gunicorn 与自己起的 redis 逐项验证。

14 次 vs 5 次
3 个 worker、限制 5 per minute 时 memory 与 redis 存储的实际放行数
5 次
fixed-window 在 0.7 秒的窗口边界放行的次数(限制 3 per 2 seconds)
47 µs
memory 存储下限流每个请求多出的耗时(test client)

固定窗口与响应头

实测 · 本机执行输出
  7 次请求的状态码: [200, 200, 200, 200, 200, 429, 429]
  第 1 次响应的限流头: {'x-ratelimit-limit': '5', 'x-ratelimit-remaining': '4', 'x-ratelimit-reset': '1790913508', 'retry-after': '60'}
  第 7 次(429)响应的头: {'x-ratelimit-limit': '5', 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': '1790913508', 'retry-after': '60'}
  429 的正文(自定义 errorhandler): {"detail":"5 per 1 minute","error":"rate limited"}

  别的客户端(X-Client: c2)不受影响: [200, 200, 200]

上一篇:Flask-Caching;下一篇:Flask-Mail。

多限制、共享、cost、exempt

实测 · 本机执行输出
  "3 per 10 seconds; 100 per hour" 连发 5 次: [200, 200, 200, 429, 429]
  共享桶 "4 per minute":交替请求 /s1 与 /s2 共 6 次: [200, 200, 200, 200, 429, 429]
  cost=5 的 "10 per minute":连发 3 次: [200, 200, 429]
  @limiter.exempt 的路由连发 30 次是否都 200: True
  只有默认限制的 /ping,头里: {'x-ratelimit-limit': '1000', 'x-ratelimit-remaining': '998', 'x-ratelimit-reset': '1790913509'}

fixed-window 与 moving-window

实测 · 本机执行输出
  fixed-window   t=0 发 1 次,t=1.5 s 连发 3 次,t=2.2 s 再连发 3 次:[200, 200, 200, 429, 200, 200, 200](限制 "3 per 2 seconds")
  moving-window  t=0 发 1 次,t=1.5 s 连发 3 次,t=2.2 s 再连发 3 次:[200, 200, 200, 429, 200, 429, 429](限制 "3 per 2 seconds")

存储与多 worker

实测 · 本机执行输出
  memory://(3 个 worker)              同一个客户端连发 30 次,限制 "5 per minute":放行 14 次
  redis://127.0.0.1:9651(3 个 worker) 同一个客户端连发 30 次,限制 "5 per minute":放行 5 次
  redis 里的键: ['LIMITS:LIMITER/rk/five/5/1/minute']
  某个限流键的值 = b'2' | TTL = 60

redis 是自己在非默认端口起的临时实例,验证完就关闭了。

key_func 与代理

实测 · 本机执行输出
  不同 X-Forwarded-For 的 7 个请求(本篇的 key_func 回退到 get_remote_address)状态码: [200, 200, 200, 200, 200, 429, 429]
  (没有配置 ProxyFix 时 remote_addr 是直连方,所以上面全算同一个客户端——见第 135 篇)

限流的开销

实测 · 本机执行输出
  无限流 69 µs/请求;有限流(memory)  116 µs/请求;多出 47 µs

分步演示

Flask-Limiter
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Flask-Limiter 4.1.1、limits 5.8.0、自己起的 redis
    • 没有验证:sliding-window-counter 策略、redis 存储每请求多出的网络往返耗时、redis 不可用时 in_memory_fallback 的行为、真实反向代理下配合 ProxyFix 的取值、分布式部署下时钟偏差的影响
    京ICP备2026067502号-1京公网安备11010502063487号