必须用 time.monotonic()。它基于单调时钟,不随系统时间回拨或 NTP 跳变而改变,可确保限流窗口连续准确,避免因时间跳变导致窗口错乱、超额放行;time.time() 映射墙钟,易受系统时间调整影响,不适用于限流场景。

限流该用 time.time() 还是 time.monotonic()?
必须用 time.monotonic()。它不随系统时间调整跳变,避免因 NTP 同步或手动改时导致窗口错乱、突发流量绕过限制。比如用户在限流窗口末尾发请求,系统时间被回拨 5 秒,用 time.time() 会误判为新窗口开始,放行超额请求。
实操建议:
- 所有基于时间窗口的限流(如每秒最多 10 次)都应以
time.monotonic()为基准计算起始时间和剩余窗口时长 - 不要用
datetime.now()或time.time()做窗口边界判断 - 若用 Redis 实现滑动窗口,
EXPIRE时间仍依赖服务端时钟,但客户端逻辑必须用单调时钟对齐语义
用 functools.lru_cache 缓存令牌桶状态靠谱吗?
不靠谱,仅适用于单进程、无并发、无重启的玩具场景。它无法跨线程/进程共享状态,且缓存键若只含用户 ID,会把不同接口的配额混在一起;一旦服务重启,全部令牌桶清零,可能引发误放行。
真实可用方案优先级:
立即学习“Python免费学习笔记(深入)”;
- 高并发 Web 服务 → 用 Redis + Lua 原子脚本实现令牌桶或滑动窗口(例如
INCR+EXPIRE组合) - 单机轻量服务(如 FastAPI 小后台)→ 用
threading.Lock+ 字典缓存,键为(user_id, endpoint),值含last_refill_time和tokens,每次请求前加锁重算令牌 - 完全规避共享状态 → 改用请求头签名+时间戳+HMAC 验证(适合 API Key 场景),但无法做动态配额
redis-py 实现滑动窗口计数时,ZREMRANGEBYSCORE 为什么总漏删旧数据?
常见错误是传入的时间戳单位与 Redis 存储单位不一致。比如 Python 用 time.time() 得到秒级浮点数(1717023456.123),但存入 ZSET 时用了 int(time.time()) 截断,而 ZREMRANGEBYSCORE 删除时却用 now - 60(仍是浮点),导致范围比较失效。
正确做法:
- 统一用整数秒:写入 ZSET 时用
int(time.monotonic())(注意:此处实际应映射到业务时间轴,通常仍用int(time.time()),但必须前后一致) - 删除命令必须匹配精度:
redis.zremrangebyscore(key, 0, now_ts - window_size),其中now_ts是同精度整数 - 务必在
ZADD后立刻EXPIREkey,防止冷 key 占满内存(比如设置EXPIRE key 86400)
Flask 中给某个路由加限流,装饰器里怎么取真实客户端 IP?
直接读 request.remote_addr 大概率拿到的是反向代理(Nginx / ALB)的内网地址。必须解析 X-Forwarded-For,但不能无条件信任——攻击者可伪造该头。
安全取法:
- 配置 Flask 的
TRUSTED_PROXIES(如app.config['TRUSTED_PROXIES'] = ['10.0.0.0/8', '192.168.0.0/16']),再启用use_x_forwarded_for=True - 或手动解析:
ip_list = request.headers.get('X-Forwarded-For', '').split(','),取最后一个非受信代理 IP(需结合request.environ.get('REMOTE_ADDR')校验链路) - 更稳妥的做法是让 Nginx 在转发时加
X-Real-IP头,并只信任该头:request.headers.get('X-Real-IP', request.remote_addr)
限流 key 若只用 IP,容易误伤 NAT 用户;建议组合 user_id(如有)或 API Key,否则至少加上接口路径,避免 /login 和 /data 共享同一配额。


















