TT Lab
开始
学习 学习路径 课程

FastAPI — 类型就是契约

验证请求限流的精确时间边界

在 TT Lab 中继续学习

目标

用假时钟检查滑动窗口和 Retry-After,并把每个用户的限额分开。

为什么重要

流量一增加,服务器就开始把所有请求记录在同一个列表里。一个用户的连续请求,把别的用户的正常请求也拦住了。从窗口的最后时刻删除条目时,比较运算也写错了,导致限制多保持了 1 秒。而真的等上几十秒的测试,会让这种边界既慢又不稳定。

步骤

  1. 在 /root/work/fa-rate-window-lab/service.py 中,validate_limit(limit, window) 只允许排除 bool 的正 int limit,以及正的有限 int/float window,并返回 (limit, float(window))。其余都是 ValueError。

先做一次准备。已有的文件不会被覆盖。

mkdir -p /root/work/fa-rate-window-lab
test -e /root/work/fa-rate-window-lab/service.py || cp /opt/fixtures/ten_labs/fa-rate-window-lab/service.py /root/work/fa-rate-window-lab/service.py
cd /root/work/fa-rate-window-lab
  1. 在 /root/work/fa-rate-window-lab/service.py 中,active(history, now, window) 只把大于 now-window 的时间,按原来的顺序作为新列表返回。history 是排好序、非递减的时间。

  2. 在 /root/work/fa-rate-window-lab/service.py 中,retry_after(history, now, window) 是:对已经清理过、非空的 history,取第一个时间+window-now 的 ceil 值与 0 中较大的整数。空列表为 0。

  3. 在 /root/work/fa-rate-window-lab/service.py 中,history_for(state, key) 在键不存在时返回空列表,存在时返回该记录的副本。不要仅仅因为查询就修改 state。

  4. 在 /root/work/fa-rate-window-lab/service.py 中,admit(state, key, now, limit, window) 在验证设置之后,清理该键的过期记录。有余量时追加 now 并返回 (True,0),已满时不追加,返回 (False,retry_after)。

  5. 在 /root/work/fa-rate-window-lab/service.py 中,client_key(value) 对 1–40 个字符的 ASCII 字母、数字、连字符的字符串原样返回,其余都是 ValueError。

  6. 在 /root/work/fa-rate-window-lab/service.py 中,limited_response(wait) 是一个 JSONResponse:状态为 429,正文为 {error:'rate_limited'},Retry-After 头是把 wait 转成字符串的值。

  7. 在 /root/work/fa-rate-window-lab/service.py 中,create_app(clock, limit=2, window=10) 在 GET /work 中检查 X-Client-ID:非法的键返回 400 {error:'invalid_client'},允许时返回 200 {ok:True},超出时返回 limited_response。state 按应用分开。

参考

验证设置

在 /root/work/fa-rate-window-lab/service.py 中,validate_limit(limit, window) 只允许排除 bool 的正 int limit,以及正的有限 int/float window,并返回 (limit, float(window))。其余都是 ValueError。

先做一次准备。已有的文件不会被覆盖。

mkdir -p /root/work/fa-rate-window-lab
test -e /root/work/fa-rate-window-lab/service.py || cp /opt/fixtures/ten_labs/fa-rate-window-lab/service.py /root/work/fa-rate-window-lab/service.py
cd /root/work/fa-rate-window-lab

bool 是 int 的子类型。NaN 和无穷大也必须另外拒绝。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/01-contract.sh 确认。

排除窗口的左边界

在 /root/work/fa-rate-window-lab/service.py 中,active(history, now, window) 只把大于 now-window 的时间,按原来的顺序作为新列表返回。history 是排好序、非递减的时间。

确认保留恰好过期的时间的 >= 与 > 的区别。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/02-contract.sh 确认。

把等待时间向上取整

在 /root/work/fa-rate-window-lab/service.py 中,retry_after(history, now, window) 是:对已经清理过、非空的 history,取第一个时间+window-now 的 ceil 值与 0 中较大的整数。空列表为 0。

如果只剩 0.2 秒就把 Retry-After 写成 0,客户端会立刻重新请求。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/03-contract.sh 确认。

按键分开记录

在 /root/work/fa-rate-window-lab/service.py 中,history_for(state, key) 在键不存在时返回空列表,存在时返回该记录的副本。不要仅仅因为查询就修改 state。

如果返回共享列表,一个请求的清理就可能改变另一个请求的记录。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/04-contract.sh 确认。

只记录被允许的请求

在 /root/work/fa-rate-window-lab/service.py 中,admit(state, key, now, limit, window) 在验证设置之后,清理该键的过期记录。有余量时追加 now 并返回 (True,0),已满时不追加,返回 (False,retry_after)。

如果追加被拒绝的请求,每次重试都会使过期时间被推后。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/05-contract.sh 确认。

验证客户端键

在 /root/work/fa-rate-window-lab/service.py 中,client_key(value) 对 1–40 个字符的 ASCII 字母、数字、连字符的字符串原样返回,其余都是 ValueError。

限制输入的范围,避免无限制的键大小挤占状态内存。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/06-contract.sh 确认。

构造拒绝响应

在 /root/work/fa-rate-window-lab/service.py 中,limited_response(wait) 是一个 JSONResponse:状态为 429,正文为 {error:'rate_limited'},Retry-After 头是把 wait 转成字符串的值。

让客户端知道重试的时间,所以状态和头一起发送。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/07-contract.sh 确认。

用虚拟时间完成请求流程

在 /root/work/fa-rate-window-lab/service.py 中,create_app(clock, limit=2, window=10) 在 GET /work 中检查 X-Client-ID:非法的键返回 400 {error:'invalid_client'},允许时返回 200 {ok:True},超出时返回 limited_response。state 按应用分开。

不要真的 sleep,而要通过 clock 函数传递装在列表里的当前时间。

保存后用 bash /opt/lab/checks/fa-rate-window-lab/08-contract.sh 确认。