防滥用与配额:一个用户吃掉了 85% 的账单
限流省 77%,但日配额才是真正封顶的那一层
ap1.6 你给产品装了成本仪表。这一节回答的是:仪表报警之后,你有什么手段?
模拟一小时的流量:100 个正常用户 + 1 个写了 for 循环的用户。
总请求 14099 次,其中滥用者 12000 次(占 85%)
A 无防护 总成本 42.30 元
其中滥用者 36.00 元(占总成本 85%)
→ 一天 1015.13 元,一个月 30453.84 元
一个人,85% 的账单。而他甚至不需要是恶意的——可能只是个测试脚本忘了关。
这一节讲三层防线:限流、配额、成本熔断。实测省下 88%,且对正常用户零误伤。
自助餐厅的三道措施:
限流 = 每次只能拿一盘(拿完再来); 配额 = 一张餐券只能进一次; 成本熔断 = 今天食材见底了,后面来的客人改上简餐——不关门,但降规格。
只有第一道的餐厅,会被一个不停往返的人吃垮。
三层防护的实测对比
【B 固定窗口限流(每人每分钟 20 次)】
放行 3299 次 总成本 9.90 元
其中滥用者 1200 次 3.60 元 (占总成本 36%)
拒绝:滥用者 10800 次 | 正常用户 0 次 ← 无误伤 ✅
【C 限流 + 日配额 50 次 + 每小时预算熔断 5 元】
放行 2149 次 实际成本 5.22 元(含 482 次降级到小模型)
其中滥用者 50 次
拒绝:滥用者 11950 次 | 正常用户 0 次 ← 无误伤 ✅
成本对比(按这一小时的流量)
A 无防护 42.30 元 → 一天 1015.13 元,一个月 30453.84 元
B 加限流 9.90 元 → 省了 77%
C 限流+日配额+成本熔断 5.22 元 → 省了 88%
关键在 B 和 C 的差别:限流把滥用者从每分钟 200 次压到 20 次——听起来解决了,但算一下:20 × 60 × 24 = 每天 28800 次。限流只是把他从"一小时烧光"变成"一天烧光"。
日配额才是真正封顶的那一层:C 里滥用者一共只拿到 50 次。
三层各管什么
| 层 | 管什么 | 典型参数 | 注意 |
|---|---|---|---|
| 限流(Rate limit) | 瞬时峰值——保护你的服务不被打垮 | 每用户每分钟 10-30 次 | 单独用它挡不住持续滥用 |
| 配额(Quota) | 总量封顶——保护你的钱包 | 免费用户每天 20-50 次 | 这一层最容易被忘掉 |
| 成本熔断 | 全局兜底——防止任何你没预料到的场景 | 每小时/每天预算上限 | 触发时降级而不是拒绝 |
第三层的实测细节值得注意:482 次请求被降级到小模型,而不是被拒绝。用户体验从"最好"变成"还能用",总成本从超预算拉回 5.22 元。拒绝是最后手段,降级永远优先(ap6.1 的降级链在这里复用)。
实现:三个小工具
① 令牌桶限流(比固定窗口更平滑,不会在整点被一波流量打穿):
class TokenBucket:
def __init__(self, capacity, refill_per_sec):
self.cap = capacity; self.tokens = capacity
self.rate = refill_per_sec; self.last = time.time()
def allow(self, n=1):
now = time.time()
self.tokens = min(self.cap, self.tokens + (now - self.last) * self.rate)
self.last = now
if self.tokens >= n:
self.tokens -= n; return True
return False
② 配额(存数据库,别只存内存——重启就清零等于没有):
-- 一行搞定,和 ap1.6 的成本流水共用一张表也行
SELECT COUNT(*) FROM llm_calls
WHERE user_id = ? AND created_at >= CURDATE(); -- 今天用了几次
③ 成本熔断(读 ap1.6 的流水,不需要新数据):
spent = today_cost() # 来自 ap1.6 的成本流水
if spent > DAILY_BUDGET:
model = CHEAP_MODEL # 降级,不拒绝
elif spent > DAILY_BUDGET * 1.5:
return "今日额度已用完,明天见" # 真的挡不住了才拒绝
异常检测:让离群的自己跳出来
不用机器学习,一个中位数就够:
u_abuse 12000 次 = 中位数的 571.4 倍 ⚠️ 离群
u078 31 次 = 中位数的 1.5 倍
u085 29 次 = 中位数的 1.4 倍
(中位数 21 次/小时。规则:超过中位数 10 倍自动告警 + 进人工复核队列)
正常用户的用量分布很紧(最高的也就中位数的 1.5 倍),滥用者是 571 倍——这种量级的差距,任何简单规则都抓得住。
要盯的三个信号:调用次数(上面这个)、单用户成本(有人专挑长文本刷)、失败率异常(有人在探测你的接口)。三个都在 ap1.6 的流水里,写个每小时跑一次的查询就行。
🔧 动手做:给你的产品上三层防护(15 分钟)
- 跑本节代码,看 A 的 42.30 元怎么变成 C 的 5.22 元,而正常用户一次都没被拒。
- 先定配额,再定限流——很多人顺序反了。问自己:一个免费用户一天用多少次是合理的?(看 ap1.6 的真实分布,取 P95 的 2 倍)
- 把配额写进数据库,和 ap1.6 的流水共用一张表。内存里的配额重启就没了。
- 加成本熔断:日预算的 100% 触发降级,150% 触发拒绝。先写降级,拒绝是最后手段。
- 加异常告警:每小时跑一次"谁超过中位数 10 倍",推到你的告警渠道。
- 写好被限流时的用户文案:告诉他什么时候恢复、怎么提额——
429加一句"请稍后再试"是最差的做法,顺手就把一个潜在付费用户赶走了。
✅ 做到这里你应该有:数据库里的日配额 + 令牌桶限流 + 成本熔断(降级优先)+ 每小时异常告警 + 一份友好的限流文案。
为什么:AI 产品和传统 Web 产品最大的不同是——每一次请求都直接花你的钱。传统产品被刷,你多买台服务器;AI 产品被刷,账单当天就到。而且 AI 工具天生吸引脚本用户:能自动化的东西,一定会有人自动化。这三层不是"以后再加的优化",是上线前的必备项。
本节模拟里正常用户零误伤,那是因为参数设得宽松。真实产品里,限流误伤的第一批人通常是重度用户,也就是最可能付费的那批。
三条实践:① 参数从 ap1.6 的真实分布来定,取 P95 的 2 倍起步,而不是拍脑袋定 10 次;② 登录用户和匿名用户分开——匿名严、登录松、付费更松,这本身就是转化设计(AP8 展开);③ 被限流时给出口:"你今天的免费额度用完了,[升级]可继续使用 / 明天 0 点恢复"——把一次拒绝变成一次转化机会,而不是把人赶走。
我的 AI 产品是【描述】,单次调用成本约【X】元,目前【有/没有】付费。我从日志里统计到的用户日调用次数分布是:中位数【X】、P90【X】、P95【X】、最大【X】。请帮我:1) 给出匿名/免费/付费三档的限流和日配额建议,并说明依据;2) 设计成本熔断的阈值和触发后的降级动作;3) 写三档用户被限制时的提示文案(要有明确的恢复时间和升级入口);4) 列出该监控的异常信号和告警阈值。
防滥用三层:限流管瞬时峰值(保护服务)、配额管总量(保护钱包)、成本熔断兜底(降级优先于拒绝);异常检测用中位数倍数就够——实测滥用者是中位数的 571 倍。
下一节回到一件很容易被忽略、但一旦出事就是大事的事:你的日志里存了什么。ap1.6 的成本流水、ap5.6 的回流样本,里面都可能有用户的真实信息。
🔎 来源与核验· 2 条,点开核对
