验证请求限流的精确时间边界
目标
用假时钟检查滑动窗口和 Retry-After,并把每个用户的限额分开。
为什么重要
流量一增加,服务器就开始把所有请求记录在同一个列表里。一个用户的连续请求,把别的用户的正常请求也拦住了。从窗口的最后时刻删除条目时,比较运算也写错了,导致限制多保持了 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
-
在
/root/work/fa-rate-window-lab/service.py中,active(history, now, window) 只把大于 now-window 的时间,按原来的顺序作为新列表返回。history 是排好序、非递减的时间。 -
在
/root/work/fa-rate-window-lab/service.py中,retry_after(history, now, window) 是:对已经清理过、非空的 history,取第一个时间+window-now 的 ceil 值与 0 中较大的整数。空列表为 0。 -
在
/root/work/fa-rate-window-lab/service.py中,history_for(state, key) 在键不存在时返回空列表,存在时返回该记录的副本。不要仅仅因为查询就修改 state。 -
在
/root/work/fa-rate-window-lab/service.py中,admit(state, key, now, limit, window) 在验证设置之后,清理该键的过期记录。有余量时追加 now 并返回 (True,0),已满时不追加,返回 (False,retry_after)。 -
在
/root/work/fa-rate-window-lab/service.py中,client_key(value) 对 1–40 个字符的 ASCII 字母、数字、连字符的字符串原样返回,其余都是 ValueError。 -
在
/root/work/fa-rate-window-lab/service.py中,limited_response(wait) 是一个 JSONResponse:状态为 429,正文为 {error:'rate_limited'},Retry-After 头是把 wait 转成字符串的值。 -
在
/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 按应用分开。
参考
- 不联网、不安装软件包,在现有的 lab-dev 环境中进行。
- 每一步都在 45 秒的评分预算内运行。不要添加真实的 sleep 或网络调用。
- 评分会重新导入提交的模块,并用独立的输入和临时 DB 进行检查。请实现契约,而不要把预期值当作常量返回。
- FastAPI 官方文档 · pytest 官方文档 · Python sqlite3
- 局限:这是一个位于进程内存中、面向单个 worker 的示例。它不保证由多个 Pod 共享的全局限额,也不保证恶意客户端的身份。X-Client-ID 是测试用的键,所以在生产环境中,应当从已认证的主体获取键。持续的时钟回拨应通过使用单调时钟来避免,本实验的 clock 是非递减的。
验证设置
在 /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 确认。