用户登录

技术文章

外部链接将在新标签页打开。

AI 流量时代的 API 限流与计费系统设计

一句话回答

核心是三层限流加三阶段计费:边缘层用 Nginx 挡量,应用层用 Redis 令牌桶原子扣减并按接口成本差异化消耗,租户层管住日/月配额与并发;计费走预扣、结算、对账,靠唯一请求 ID 保证幂等不重不漏。

核心要点 TL;DR
  • 限流分三层:Nginx 边缘挡量、Redis 令牌桶算账、租户配额控总量
  • 必须带 Retry-After 与剩余额度,否则 AI 智能体会把限流变放大器
  • 计费拆成预扣、结算、对账三阶段,唯一请求 ID 保证幂等不重不漏
  • AI 客户端带来重试风暴、并发占用与 Key 克隆三类新问题
  • 对账差额超千分之一就告警并冻结账户,不要自动修正

引言:调用方变了,限流与计费也必须跟着变#

2026 年的公共 API 平台上,请求的来源正在悄悄换人。过去是一个开发者写一段脚本,按需调用几次接口;现在是 IDE 里的 AI 编码助手、自动化智能体、批量任务框架在替你调用——它们会在失败后毫秒级重试,会在一分钟内并发打满配额,也会把别人调通的调用方式原样克隆过去。Valve 近期回应开发者对 AI 生成的低质内容与克隆作品泛滥的担忧,本质上和平台面对的是同一个问题:当机器成为主要生产者,规则必须从“相信调用方”转向“验证调用方”。

对 API 平台来说,这个转向最终落在两个子系统上:限流决定谁能用、用多快,计费决定用多少、付多少。二者共享同一份计量数据,却又有着完全不同的失败代价——限流误判只是让用户等几秒,计费出错则会让账目和信任同时崩掉。本文以 API.PLUS 这类提供 IP 查询、随机动漫壁纸等免费接口的公共服务为背景,讲一套可落地的设计。

一、先把“稳定”拆成四个可测量的目标#

“稳定”是个形容词,不是指标。设计之前,先把它拆成四条可以写进 SLO 的约束:

目标含义可观测方式
可用性限流组件自身故障时,业务接口仍可服务Redis 不可用时的降级成功率
公平性单个租户或单个 IP 不能挤占全局容量Top10 租户 QPS(Queries Per Second,每秒请求数) 占比
账目一致性计费允许延迟,不允许重复或遗漏日终对账差额率
可解释性每个 429 都能回答“为什么被拒、何时恢复”429 响应体完整率

这四条里,最容易被忽略的是最后一条。AI 智能体拿到一个没有 Retry-After、没有错误码的 429,最常见的反应不是停下,而是立刻重试——于是限流器反而成了放大器。所以拒绝请求时,一定要把原因、剩余额度、恢复时间写清楚。

二、三层限流:边缘挡量、应用算账、租户控总量#

1. 边缘层:用 Nginx 做第一道粗筛

绝大多数刷量请求连业务代码都不该进。把基于 IP 和 API Key 的限流放在反向代理层,成本最低、见效最快:

示例说明:以下代码仅用于逻辑验证,生产环境需补充异常处理、超时与重试、日志与监控告警及降级策略。

limit_req_zone $binary_remote_addr zone=api_per_ip:20m rate=10r/s;
limit_req_zone $http_x_api_key zone=api_per_key:40m rate=50r/s;
limit_req_status 429;

server {
  location /api/ {
    limit_req zone=api_per_key burst=100 nodelay;
    limit_req zone=api_per_ip burst=20 nodelay;
    add_header Retry-After 1 always;
    add_header X-RateLimit-Scope "key" always;
    proxy_pass http://127.0.0.1:9000;
  }
}

边缘层的定位是“防雪崩”,不是“算钱”。它的速率必须比业务层宽松,否则真实用户会被误伤,而计费精度也不该依赖 Nginx 的日志。

2. 应用层:Redis 令牌桶,一次 Lua 脚本搞定读取与扣减

真正的配额判断要在应用层完成,因为这里能拿到租户身份、接口权重和套餐等级。用一个原子 Lua 脚本实现令牌桶,避免“先 GET 再 SET”带来的并发穿透:

-- KEYS[1] = 限流键(tenant:endpoint)
-- ARGV[1] = 桶容量,ARGV[2] = 每秒补充速率
-- ARGV[3] = 当前毫秒时间戳,ARGV[4] = 本次消耗令牌数
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])

local data = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(data[1])
local ts = tonumber(data[2])
if tokens == nil then tokens = capacity; ts = now end

local delta = math.max(0, now - ts) / 1000.0
tokens = math.min(capacity, tokens + delta * rate)

local allowed = 0
if tokens >= cost then
  tokens = tokens - cost
  allowed = 1
end

redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(capacity / rate * 1000) + 1000)

if allowed == 0 then
  return {0, math.floor(tokens), math.ceil((cost - tokens) / rate * 1000)}
end
return {1, math.floor(tokens), 0}

返回值的第三个字段是“还需等待多少毫秒”,直接写进 Retry-After 和响应体,客户端就知道该怎么退避。对于像 IP 查询接口 这种轻量只读接口,一次请求消耗 1 个令牌;对于会触发上游抓取的 随机动漫壁纸接口,建议消耗 2~5 个令牌,让不同成本的操作在同一个桶里体现差异。

3. 租户层:日/月配额与并发上限

速率限流管的是“多快”,配额管的是“总共多少”。两者缺一不可:一个智能体完全可能以合规速率跑满整月额度。配额用 Redis 计数器实现,按 tenant:2026-10-28 建键,超出后返回业务错误码而不是 429,便于调用方区分“限速”和“用完了”。另外对长耗时接口再加一个并发信号量,防止少量慢请求把工作进程全部占住。

三、计费:预扣、结算、对账三步走#

计费最容易出事的地方,是把“扣费”和“业务调用”写在同一个事务里。正确做法是拆成三阶段,允许最终一致:

  1. 预扣:请求进入时按最大可能消耗预占额度,写一条 pending 记录;
  2. 结算:调用成功后按实际消耗修正,失败则释放;
  3. 对账:日终把明细汇总到日账单,与额度账户核对。

支撑这一切的核心是幂等。每个请求必须携带或生成一个全局唯一 ID(推荐 ULID,时间有序便于分片),数据库以它建唯一索引:

CREATE TABLE usage_event (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  request_id CHAR(26) NOT NULL,
  tenant_id BIGINT UNSIGNED NOT NULL,
  api_key_id BIGINT UNSIGNED NOT NULL,
  endpoint VARCHAR(128) NOT NULL,
  units INT NOT NULL DEFAULT 1,
  cost_micro BIGINT NOT NULL,
  occurred_at DATETIME(3) NOT NULL,
  UNIQUE KEY uk_request (request_id),
  KEY idx_tenant_time (tenant_id, occurred_at)
) ENGINE=InnoDB;

有了唯一索引,重试、消息重投、补偿任务都不会重复计费。业务侧的骨架大致如下:

def handle(request):
    rid = request.headers.get('X-Request-Id') or new_ulid()
    if not reserve(tenant_id, rid, estimated_units):
        return error(429, retry_after_ms=next_available(tenant_id))
    try:
        result = call_upstream(request)
    except RetryableError:
        release(tenant_id, rid)   # 上游失败不计费
        raise
    settle(tenant_id, rid, actual_units(result))
    return result

注意两点:其一,estimated_units 要给足冗余,宁可预扣多一点在结算时退回,也不要中途追加导致扣费失败;其二,明细表写入必须异步批量落库,同步写库会把接口 P99(99 分位延迟) 直接拖垮。对账时只做一件事:把 usage_event 按租户汇总,与账户流水比对,差额率超过千分之一就告警并冻结异常账户,而不是自动修正——账目自动修正会掩盖真实故障。

四、AI 客户端带来的三个新问题#

重试风暴

智能体默认会重试,而且不区分错误类型。对策是明确区分可重试与不可重试:429 和 5xx 返回结构化错误体并附 Retry-After,4xx 校验错误直接返回终态。同时在文档中建议指数退避加随机抖动——这一点比任何服务端优化都有效。

并发占用与慢请求

AI 工具倾向于一次性发起大批并发。如果只用令牌桶,瞬时并发仍可能打满连接池。补充一个“在途请求数”限制,并对超过阈值的长尾请求直接拒绝,比让它们排队更友好。

克隆与滥用

当调用方式可以被轻易复制,API Key 就会变成事实上的公共资源。常见做法是 Key 与来源绑定(可选 Referer/Origin 白名单)、单 Key 设备指纹数量上限、以及基于调用模式的异常检测(同一 Key 在极短时间内出现大量不同 UA 与出口 IP)。这些手段不必全上,但至少要能回答“这个 Key 现在被谁在用”。

五、观测与压测:让限流器自己也被监控#

  • 指标:限流命中率、429 占比、令牌桶平均余量、预扣未结算笔数、对账差额率;
  • 日志:每个 429 记录租户、接口、触发规则、剩余额度,采样不必 100%,但被投诉的请求必须可查;
  • 压测:不要只压正常路径,专门压“限流器与被限流器同时高负载”的场景,观察 Redis 抖动时降级逻辑是否生效;
  • 灰度:新增限流规则先在 1% 流量上以“只记录不拦截”的影子模式跑一天,确认真实用户不会被打到再放开。

压测时可以直接用平台上的真实接口作为被测目标,例如 curl -s 'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8' 与 curl -s 'https://www.api.plus/api/dongman?type=json',前者是纯计算型,后者可能涉及外部数据拉取,两者混合压测更能暴露配额分配的偏斜问题。更多响应格式约定可以参考 API 文章中心 中关于 JSON 与图片接口响应设计的说明,POST 类接口的幂等示例可查看 POST 示例接口。

六、总结#

一套稳定的 API 限流与计费系统,本质是把“信任调用方”换成“验证调用方”,同时保留可解释性:边缘层挡住雪崩,应用层用原子令牌桶算准每一次消耗,租户层管住总量,计费层用唯一请求 ID 保证不重不漏,最后用日终对账兜底。面向 AI 客户端时,还要额外关照重试、并发与克隆这三件事——它们不会因为限流做得好就消失,只会因为响应头写得好而收敛。

如果你正在为项目挑选稳定的公共接口,可以直接在 API.PLUS 注册账号获取免费 API Key,先用 IP 查询接口 或 随机动漫壁纸接口 跑通调用链路,再结合上面的限流思路做客户端侧的退避与缓存;遇到配额或错误码相关的问题,可通过 技术支持 反馈。

常见问题(FAQ)

为什么要把限流做成边缘、应用、租户三层,而不是只做一层?
三层职责不同:Nginx 边缘层成本最低,负责挡住刷量雪崩;应用层能拿到租户身份与接口权重,用 Redis 令牌桶原子扣减算准每次消耗;租户层管日/月配额与并发上限,防止智能体以合规速率跑满整月额度。缺任何一层都会留下缺口。
429 响应为什么必须写清原因、剩余额度和恢复时间?
AI 智能体拿到没有 Retry-After 和错误码的 429,通常不会停下而是立刻重试,限流器反而成了放大器。所以拒绝请求时要返回结构化错误体与等待毫秒数,并建议客户端指数退避加随机抖动,这比任何服务端优化都有效。
计费如何避免重试和消息重投导致重复扣费?
为每个请求生成或携带全局唯一 ID(推荐时间有序的 ULID),在用量明细表上建唯一索引,重试、消息重投和补偿任务都无法重复写入;同时把扣费拆成预扣、结算、对账三阶段,允许最终一致,并靠日终对账兜底。
不同成本的接口如何在同一个令牌桶里体现差异?
用消耗令牌数区分:IP 查询这类轻量只读接口一次消耗 1 个令牌,会触发上游抓取的随机动漫壁纸接口建议消耗 2~5 个令牌,让高成本操作占用更多配额,避免低成本接口把额度吃光。
限流规则上线前怎么验证不会误伤真实用户?
新增规则先以影子模式在 1% 流量上只记录不拦截,跑一天确认真实用户不会被打到再放开;同时监控限流命中率、429 占比、令牌桶平均余量和预扣未结算笔数,并专门压测限流器与被限流器同时高负载的场景。

作者:沐小悠

本文约 13 分钟读完 · 关键词:API 限流、API 计费、令牌桶、Redis、配额管理

更多文章 GET 示例接口

「」标签下的其他文章