稳定 API 限流与计费系统设计:计量、账本与降级
稳定 API 限流与计费必须共享同一套数据:先统一权重与用量事件口径,再用 Redis 令牌桶分级限流,并以预扣—结算—对账账本和依赖降级兜底,才能做到不超卖、不丢事件,又能用每日对账证明账目一致。
- 先统一计量口径(权重、用量事件),再选限流算法
- 令牌桶用 Redis+Lua 原子扣减,429 返回 Retry-After
- 计费采用预扣—结算—退款三段式,request_id 幂等防重复扣费
- 日切后做三方对账,差异超阈值即告警并冻结自动结算
- 依赖故障须降级:Redis 挂切进程内桶,队列失败先写本地磁盘再补偿
引言:当调用方从人变成 Agent#
2026 年,API 的调用方越来越多是 Agent,而不是人。一个 Agent 为了完成「查 IP 归属地、再配一张图」这样的任务,可能在几秒内循环几十次请求;新出现的 AI 原生语言(例如强调 Rust 性能、Python 语法的 Vu)也在降低生成高并发客户端的门槛。结果是两个变化同时发生:流量更突发,成本更难归属。限流回答「这次请求能不能进」,计费回答「进来之后花了多少」,两者共享同一份计量事实,必须一起设计。本文给出一套可落地的结构:统一计量模型、Redis 令牌桶、预扣—结算—对账账本,以及依赖故障时的降级策略。
一、先定计量模型,再选限流算法#
很多系统先写限流,再补计费,最后发现两边的口径对不上:限流按 QPS(Queries Per Second,每秒请求数) 算,计费按调用次数算,图片接口和 JSON 接口成本不同却同价。正确的顺序是先定义「一次调用消耗多少资源」,再决定用什么算法去限制它。
- 计量单位:请求次数、返回条数、Token 数、计算时长、出流量。聚合型 API 建议用「权重」统一表达,例如普通 JSON 查询权重为 1,返回高清图片的接口权重为 3。
- 用量事件:每次调用生成一条不可变事件,字段至少包含 request_id、api_key_id、endpoint、units、unit_price、status、created_at。request_id 同时作为幂等键,贯穿限流、计费与排查。
- 快慢分离:限流走 Redis 计数,要求毫秒级;计费走消息队列落库,允许秒级延迟,但不允许丢事件。可以先把事件写入本地 outbox 再返回响应,由后台任务搬运。
| 维度 | 限流侧 | 计费侧 |
|---|---|---|
| 一致性要求 | 近似即可,允许少量误差 | 最终一致,事件不能丢 |
| 主要存储 | Redis 计数器 / 令牌桶 | MySQL 账本 + 消息队列 |
| 失败策略 | 按接口分级 fail-open 或 fail-closed | 重试、死信、本地落盘补偿 |
| 时间粒度 | 秒级 | 账单周期 + 日切对账 |
二、限流:令牌桶 + 分级配额 + 原子扣减#
算法选择上,固定窗口在边界会产生双倍突刺;滑动日志精度高,但内存随 QPS(Queries Per Second,每秒请求数) 增长;令牌桶允许有限突发,最适合开放 API。工程上通常分三层:全站保护防雪崩、租户或套餐配额限制日/月总量、Key 级 QPS 防单点刷量,再加一层 IP 兜底,防止同一来源批量注册。
扣减必须原子。下面是用 Redis + Lua 实现的令牌桶,时间取 Redis 服务端 TIME,避免多实例时钟漂移:
示例说明:以下代码仅用于逻辑验证,生产环境需补充异常处理、超时与重试、日志与监控告警及降级策略。
-- KEYS[1]=bucket key ARGV[1]=rate(个/秒) ARGV[2]=burst ARGV[3]=now_ms
local rate = tonumber(ARGV[1])
local burst = tonumber(ARGV[2])
local t = redis.call('TIME')
local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)
local d = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(d[1]) or burst
local ts = tonumber(d[2]) or now
tokens = math.min(burst, tokens + math.max(0, now - ts) / 1000 * rate)
local allowed = 0
if tokens >= 1 then
tokens = tokens - 1
allowed = 1
end
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(burst / rate * 1000) + 1000)
return {allowed, math.floor(tokens), math.ceil((1 - tokens) / rate * 1000)}几个容易忽略的细节:
- key 带时间片(如 rl:{key_id}:{分钟})可以打散热点,但令牌桶本身是单 key,上线前要评估分片与容量。
- 拒绝时返回 429,并带上 Retry-After 与 X-RateLimit-Remaining,Agent 客户端才能正确退避,而不是立即重试放大流量。
- 限流组件不可用时要明确策略:查询类接口 fail-open 并记录降级日志,写类或高成本接口 fail-closed。策略写进配置,不要散落在业务代码里。
- 慢接口加并发闸门(信号量),防止连接被拖满,这往往比单纯限制 QPS(Queries Per Second,每秒请求数) 更有效。
三、计费账本:预扣、结算与对账#
「先查余额,再扣减」在并发下必然超卖。稳妥做法是三段式:reserve 预扣本次调用的最大可能费用,commit 按实际用量结算,refund 释放差额。request_id 加唯一索引,保证重复请求不会重复扣费。
CREATE TABLE usage_event (
request_id CHAR(26) PRIMARY KEY,
api_key_id BIGINT NOT NULL,
endpoint VARCHAR(64) NOT NULL,
units INT NOT NULL,
unit_price DECIMAL(12,6) NOT NULL,
status SMALLINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_key_time (api_key_id, created_at)
);
CREATE TABLE balance_ledger (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
api_key_id BIGINT NOT NULL,
request_id CHAR(26) NOT NULL,
delta DECIMAL(14,6) NOT NULL,
reason VARCHAR(32) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_request_reason (request_id, reason)
);账本要双录:用户余额变动与平台收入流水在同一事务里写,或者通过 outbox 保证最终一致。流式响应、图片下载这类「开始计费但可能中途断开」的场景,按已产生用量结算,未使用部分退回,避免用户为失败请求付费。
对账是稳定性的最后一道闸。每天日切后做三方比对:usage_event 汇总、balance_ledger 汇总、上游供应商账单。差异超过阈值(比如 0.5%)就告警并冻结自动结算,转人工排查。对账脚本要可重跑,按账期幂等,否则补数会引入二次错误。
四、降级、可观测与灰度#
限流和计费系统本身也会故障,所以要按「依赖一定会挂」来设计:Redis 不可用时切进程内令牌桶,精度下降但服务可用;队列写入失败落本地磁盘并后台补偿;计费服务超时不能阻塞业务响应,改为异步补记。所有降级都要有开关和日志,事后能还原当时发生了什么。
- 核心指标:拒绝率、429 占比、鉴权 P99(99 分位延迟) 延迟、队列积压、负余额请求数、对账差异金额。
- 影子模式:新算法先只记录不拦截,跑一周对比放行率与误杀率,再灰度切流。
- 安全审计:Key 轮换、按 Key 记录调用画像,识别同一 IP 多 Key、同一 Key 多地域等异常模式。
五、用真实接口做一次端到端演练#
把上面的结构接到真实接口上验证最直接。先注册拿到 Key,用 IP 查询接口压测 Key 级 QPS(Queries Per Second,每秒请求数):
curl -i 'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8'观察响应头中的限流字段,确认 429 时 Retry-After 是否合理。再用随机动漫壁纸接口验证「按响应体积或权重计费」:
curl -i 'https://www.api.plus/api/dongman?type=json'然后用 POST 示例接口 验证幂等:同一个 request_id 连发两次,账本只应出现一条记录。接口参数见 IP 查询接口文档,更多实现细节可以翻 API 文章中心,遇到接入问题走 技术支持。建议压测脚本固定并发与时长,记录每秒放行数、拒绝数与账本增量,三者应当自洽。
六、总结#
稳定的限流与计费系统可以浓缩成三条原则:计量先行,先统一用量口径再谈算法;预扣后结算,用 request_id 幂等和账本双录消除超卖;可降级可对账,依赖故障时有兜底,每天能用数字证明账是平的。算法不是难点,难点在于让限流与计费共享同一份事实,并在故障时仍然可解释。
如果你正在做自己的 API 服务,可以先用小流量把这条链路跑通。前往 API.PLUS 注册即可获取免费 API Key,用 IP 查询、动漫壁纸等接口做限流与计费联调,成本低、反馈快。