本文目录导读:

“调用次数限制”是个很常见的需求,但具体怎么实现取决于你在哪里限制(客户端还是服务端)以及你的技术栈,下面分几种常见场景给你讲清楚:
最通用:服务端 API 限流(最推荐)
这是最安全的做法,完全由后端控制,前端无法绕过。
技术手段:令牌桶算法 / 漏桶算法 / 滑动窗口
使用 Redis + Lua 脚本来实现滑动窗口(伪代码示例)
import time
import redis
r = redis.Redis()
def check_rate_limit(user_id, action, max_requests=10, window_seconds=60):
"""检查用户是否超限,例如每分钟最多10次"""
key = f"rate_limit:{user_id}:{action}"
now = time.time()
window_start = now - window_seconds
# 使用 Redis 有序集合
# 移除窗口外的请求记录
r.zremrangebyscore(key, 0, window_start)
# 获取当前窗口内的请求数
current_count = r.zcard(key)
if current_count >= max_requests:
return False # 拒绝
# 记录本次请求
r.zadd(key, {now: now})
r.expire(key, window_seconds) # 自动过期
return True # 允许
使用现成的限流框架(更省心)
- Java: Sentinel, Resilience4j, Guava RateLimiter
- Python: Flask-Limiter, FastAPI-Limiter
- Go:
golang.org/x/time/rate - Nginx: 直接在反向代理层限制(如
limit_req_zone)
前端控制(防君子不防小人,用于提升体验)
前端限制主要用于减少无效请求,不能作为安全手段(因为用户能轻易修改代码)。
按钮防抖(Debounce)
适用于搜索框、提交按钮:
const debounce = (fn, delay = 500) => {
let timer = null;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
};
};
// 用法
const search = debounce((keyword) => {
API.search(keyword);
}, 300);
节流(Throttle)
适用于滚动加载、频繁点击:
const throttle = (fn, limit = 1000) => {
let inThrottle = false;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};
// 用法
const clickHandler = throttle(() => {
API.submit();
}, 2000);
调用后禁用按钮
最简单的体验优化:
const handleSubmit = async () => {
btn.disabled = true;
btn.textContent = '提交中...';
try {
await API.submit(data);
} finally {
btn.disabled = false;
btn.textContent = '提交';
}
};
不同维度的限制策略
| 维度 | 典型场景 | 限制方式 |
|---|---|---|
| 用户 | 每个用户每分钟只能发10条消息 | user_id 作为 Key |
| IP | 未登录用户,每个IP每天100次请求 | ip 作为 Key(注意代理穿透问题) |
| API Key | Open API 调用,每个Key 100次/小时 | api_key 作为 Key |
| 全局 | 防止DDoS,整个服务最多1万QPS | Nginx / 网关层限流 |
| 资源 | 某个接口调用太贵,限制总次数 | 配合数据库记录消耗次数,达到阈值后返回 429 |
HTTP 状态码与返回规范
当触发限制时,应该:
- 返回 HTTP 429(Too Many Requests)
- 在响应头告知客户端:
Retry-After: 3600// 多久后再试(秒)X-RateLimit-Limit: 10X-RateLimit-Remaining: 0X-RateLimit-Reset: 1700000000// 重置时间戳
场景举例:API 调用次数的具体实现
假设你有一个 AI 聊天功能,要限制每个用户每天只能调用 20 次:
# 数据库方案(日更简单)
def check_daily_limit(user_id):
key = f"user:{user_id}:daily_call"
today = date.today().isoformat()
# 获取今日已用次数
used = cache.get(f"{key}:{today}") or 0
if used >= 20:
return False
# 增加计数,设置过期时间为明天
cache.incr(f"{key}:{today}")
cache.expire(f"{key}:{today}", 86400)
return True
哪种方案适合你?
| 你的情况 | 推荐方案 |
|---|---|
| 自己写的前后端项目,简单控制用户调用 | 后端 Redis + 滑动窗口 |
| 调别人的 API(如 ChatGPT / 百度翻译) | 客户端节流+后端记录消耗日志 |
| 小程序 / 普通 Web 项目 | 后端限流框架 + 前端防抖 |
| 大型分布式系统 | API 网关(Kong / Nginx) + 分布式限流 |
- 安全:一定要在后端限制,不要相信前端。
- 体验:前端加防抖/节流,避免用户频繁点击。
- 实现:用 Redis + 滑动窗口是最通用的做法,用现成库(如 Flask-Limiter)最快。
你想限制的具体场景是什么?比如是用户调用 ChatGPT API,还是你的后端接口被外部调用?告诉我细节,我可以给你更精准的代码。