周三凌晨两点,某电商平台的促销系统突然崩溃。不是代码bug,也不是数据库挂了——是竞争对手用脚本疯狂调用价格接口,每秒10万次请求直接把服务器打穿。运维团队花了四小时才恢复,损失订单超过200万。

这种事每天都在发生。如果你的API对外开放,或者系统承载高并发流量,限流(Rate Limiting)不是可选项,是保命项。没有它,DDoS攻击、暴力破解、数据爬虫随时能让你的服务"下线"。

打开网易新闻 查看精彩图片

限流不是简单的"数请求个数"。下面这五种算法,从入门到高阶,决定了你的系统能扛住多大压力。

固定窗口计数器:最简单的陷阱

原理很直白:设定一个时间窗口,比如每分钟最多100次请求。从00:00到00:59算一个窗口,满了就拒绝,下个窗口清零重计。

致命弱点在窗口交界处。攻击者在00:58狂刷100次,00:01又刷100次——两秒内200次请求,系统直接过载。这个漏洞太经典,以至于生产环境几乎没人敢单用它。

滑动窗口日志:精准但烧钱

改进方案是记录每个请求的时间戳。系统随时检查"过去60秒内有多少条记录",精确到毫秒级控制。

代价是内存爆炸。百万级用户、每秒上千请求,日志数据能把Redis撑爆。除非你的用户量极小,否则这条路走不通。

滑动窗口计数器:平衡之道

前两者的 hybrid 方案。不存完整日志,而是把窗口切分成更小的子窗口(比如1分钟切成6个10秒块),按权重估算当前窗口的请求量。

误差可控,内存友好,大多数中型系统的选择。

令牌桶:大厂的默认配置

Amazon、Stripe 都在用的算法。想象一个桶,系统以固定速率往里面放令牌,每个请求必须拿到令牌才能通过。桶有容量上限,满了就不再放令牌。

关键特性:允许突发流量。桶里有100个令牌时,瞬间来100个请求都能通过,之后回归匀速。这对用户体验至关重要——没人想每次点击都等匀速放行。

漏桶:平滑流量的手术刀

请求先进入桶,再以固定速率"漏"出去处理。和令牌桶相反,它压制突发,强制流量平滑。

适合内部服务调用,或者对延迟不敏感的后台任务。用户端接口用它,体感会很卡。

动手:Python+Redis 的最小实现

下面这段代码演示固定窗口的核心逻辑,用Redis的pipeline保证操作原子性:

import redis

import time

r = redis.Redis(host='localhost', port=6379, db=0)

def is_request_allowed(user_id, limit, window_seconds):

key = f"rate_limit:{user_id}"

pipe = r.pipeline()

pipe.incr(key, 1)

pipe.expire(key, window_seconds)

results = pipe.execute()

return results[0] <= limit

代码看起来能跑,但生产环境有个隐形炸弹:race condition。当两个请求同时检查计数、同时发现"还没超",结果都放行,实际请求数就翻倍了。

真正的解决方案是用Lua脚本在Redis端原子执行"检查+递增"整个流程。上面的代码只是教学演示,上线必改。