已发布·持续改进
Algorithm
限流按用户或 IP 限制单位时间内的请求数,防止过载与滥用。本文介绍令牌桶、滑动窗口、漏桶以及基于 Redis 的分布式实现。
限流器(rate limiter)用于控制一段时间内接受的请求数量。先确定键、上限和时间窗口,例如“每个用户每分钟 100 次”,超出部分的请求会被拒绝或延后处理。经典算法有固定窗口、滑动窗口日志、滑动窗口计数器、令牌桶和漏桶,它们在精度、内存占用和突发流量处理方式上各不相同。
几乎所有对外开放的功能,从公开 API 到登录、支付和消息发送,都部署在限流器之后。它保护服务器免受流量突增和失控的重试循环冲击,防止单个用户独占共享资源,并减缓暴力破解攻击。限流也是系统设计面试中的常见题目,需要能讲清各算法的取舍以及在多台服务器上的实现。
建议先动手写一个固定窗口计数器,观察它在窗口边界处放过两倍上限的问题,再用滑动窗口和令牌桶加以改进。之后可以学习注入时钟进行测试、返回 HTTP 429 与 Retry-After,以及用 Redis 的 INCR、EXPIRE 和 Lua 脚本让多台服务器共享同一个限额。
令牌以固定速率补充,每个请求消耗一个;既保证平均速率,又允许不超过桶容量的突发流量。
通过时间戳日志,或对前后两个窗口计数加权估算,统计最近一段时间的请求数,避免固定窗口的边界突刺。
按用户 ID、API 密钥、IP 地址或接口分别计数,防止单个客户端独占资源。
借助 Redis 的原子 INCR 和 Lua 脚本,多台服务器可以在没有竞态条件的情况下共享同一个限额。
TokenBucket 每秒补充 rate 个令牌,最多容纳 capacity 个。allow() 会一次性补上自上次调用以来应得的令牌(惰性补充),令牌足够时消耗一个并返回 True。运行后,前 5 次调用作为突发流量通过,随后 3 次被拒绝;暂停 1.1 秒后又有 2 次通过。时间用不会倒退的 time.monotonic() 测量。
token_bucket.py
import time
class TokenBucket:
"""Allow bursts of up to `capacity` requests, refilled at `rate` tokens per second."""
def __init__(self, rate: float, capacity: float) -> None:
self.rate = rate
self.capacity = capacity
self.tokens = capacity
self.updated = time.monotonic()
def allow(self, cost: float = 1.0) -> bool:
now = time.monotonic()
self.tokens = min(self.capacity, self.tokens + (now - self.updated) * self.rate)
self.updated = now
if self.tokens >= cost:
self.tokens -= cost
return True
return False
if __name__ == "__main__":
bucket = TokenBucket(rate=2, capacity=5) # 2 requests per second, bursts of 5
print([bucket.allow() for _ in range(8)]) # 5 x True, then 3 x False
time.sleep(1.1) # about 2.2 tokens come back
print(bucket.allow(), bucket.allow(), bucket.allow()) # True True False
python token_bucket.py共六章,带你从安装一步步了解 限流 的核心概念。
在这里提问、分享经验,交流关于 限流 的看法。
还没有讨论。来发起第一个吧。
0 条评论
登录 · 登录后即可发表评论。
来发表第一条评论吧。