1. 为什么你的Agent总是被限流?
最近在开发一个自动化Agent时,我遇到了一个令人头疼的问题:频繁收到429(Too Many Requests)状态码。这就像在社交场合中,如果你不停地向别人提问而不给对方喘息的机会,最终会被礼貌地请出门外。服务器也是如此,它会通过429状态码告诉你:"嘿,慢一点,我需要休息一下。"
这个问题在调用第三方API时尤为常见。大多数API服务商都会设置速率限制(Rate Limit),比如:
- 每分钟最多60次请求
- 每10秒不超过20次请求
- 每天总量不超过5000次请求
当你的请求频率超过这些限制时,服务器就会返回429错误。这不仅会导致当前请求失败,如果处理不当,还可能被临时封禁IP。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 理解HTTP 429与限流机制
2.1 429状态码的含义
HTTP 429状态码表示用户在给定时间内发送了太多请求("rate limiting")。与5xx服务器错误不同,这是客户端行为导致的问题。服务器通常会在响应头中包含以下关键信息:
Retry-After: 建议客户端等待多少秒后再重试X-RateLimit-Limit: 单位时间内允许的最大请求数X-RateLimit-Remaining: 当前时间段内剩余的请求数X-RateLimit-Reset: 限制重置的时间戳
2.2 常见的限流算法
服务器端通常采用以下几种限流算法:
-
固定窗口计数器:
- 将时间划分为固定长度的窗口(如1分钟)
- 每个窗口内请求数不能超过阈值
- 简单但可能在窗口交界处出现请求突增
-
滑动窗口日志:
- 记录每个请求的时间戳
- 统计最近时间窗口内的请求数
- 更精确但内存消耗较大
-
令牌桶算法:
- 系统以固定速率向桶中添加令牌
- 每个请求需要消耗一个令牌
- 当桶空时拒绝新请求
- 允许一定程度的突发流量
-
漏桶算法:
- 请求以固定速率被处理
- 超过容量的请求会被丢弃或排队
- 严格限制请求速率
3. 设计优雅的重试逻辑
3.1 基础重试策略
最简单的重试逻辑是固定间隔重试:
python复制import time
import requests
def make_request(url, max_retries=3, retry_delay=1):
for attempt in range(max_retries):
try:
response = requests.get(url)
if response.status_code != 429:
return response
time.sleep(retry_delay)
except Exception as e:
if attempt == max_retries - 1:
raise
time.sleep(retry_delay)
return None
但这种方案有两个明显问题:
- 固定的重试延迟可能过长(浪费资源)或过短(仍然被限流)
- 没有考虑服务器返回的
Retry-After提示
3.2 进阶:指数退避算法
指数退避(Exponential Backoff)是一种更智能的重试策略,每次重试的间隔时间按指数增长:
python复制import random
def exponential_backoff(base_delay=1, max_delay=60, factor=2):
delay = base_delay
while True:
yield delay
delay = min(delay * factor, max_delay) * (1 + random.random() / 4) # 添加一些随机性
应用示例:
python复制backoff = exponential_backoff()
for attempt in range(max_retries):
try:
response = requests.get(url)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', next(backoff)))
time.sleep(retry_after)
continue
return response
except Exception:
time.sleep(next(backoff))
3.3 结合令牌桶的限流器
我们可以实现一个客户端限流器,主动控制请求速率:
python复制from threading import Lock
import time
class RateLimiter:
def __init__(self, rate, period):
self.rate = rate # 允许的请求数
self.period = period # 时间窗口(秒)
self.tokens = rate
self.last_refill = time.time()
self.lock = Lock()
def get_token(self):
with self.lock:
now = time.time()
elapsed = now - self.last_refill
# 根据时间流逝补充令牌
if elapsed > self.period:
self.tokens = self.rate
self.last_refill = now
else:
new_tokens = elapsed * self.rate / self.period
self.tokens = min(self.rate, self.tokens + new_tokens)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
# 使用示例
limiter = RateLimiter(rate=60, period=60) # 每分钟60次
def make_request(url):
while not limiter.get_token():
time.sleep(0.1)
return requests.get(url)
4. 实战:完整的社交限流处理方案
4.1 请求拦截器设计
我们可以创建一个请求拦截器,统一处理所有HTTP请求:
python复制import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class PoliteRequestSession:
def __init__(self):
self.session = requests.Session()
# 配置重试策略
retry_strategy = Retry(
total=5,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
respect_retry_after_header=True
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
