AI 大模型评测平台对接多家模型做批量推理,单日 API 调用可达数十万次,成本随 token 消耗实时累积。用 Redis 的原子计数(INCRBY)维护用量、用键过期(TTL)划分时间窗口、用发布订阅(Pub/Sub)推送预算告警,能在毫秒级延迟内完成”扣量—比对阈值—告警”的闭环,避免月末账单爆雷。下文给出可直接落地的键模型、阈值表与 Spring Boot 代码。
需要说明:本文基于通用工程实践对该平台架构做合理推演,平台真实源码不在本次讨论范围内。所提方案可在任意 Spring Boot + Redis 技术栈上复现,读者应结合自身供应商计费口径做字段对齐。
一、为什么评测平台必须做实时成本监控
批量评测把同一批题目并发打给多个模型,同一份 prompt 可能反复计费。等到供应商月结账单才发现问题,损失已经既成事实。把计量移到请求链路上游,边调边扣,才能在超预算前截断任务。
| 计量方案 | 数据落点 | 计数精度 | 实时性 | 运维成本 |
|---|---|---|---|---|
| 应用内存计数 | JVM 堆 | 高 | 高 | 低,但重启即丢 |
| 数据库行更新 | MySQL 表 | 高 | 低(写盘锁竞争) | 中,需建表与索引 |
| 离线日志统计 | 数仓 / ES | 中 | 差(T+1) | 高 |
| Redis 原子计数 | 内存 + 持久化 | 高 | 高(亚毫秒) | 中,复用现有缓存实例 |
Redis 在单线程模型下天然串行化写命令,INCRBY 无需应用层加锁即可保证并发计数一致,契合”用量只增不减、丢失即坏账”的计量场景。评测平台本就常引入 Redis 做缓存或会话,顺带承担成本计量几乎零额外基础设施成本。
二、用 Redis 原子计数构建实时用量
2.1 按维度拆分计数键
把一次模型调用的 token 成本拆成三个维度分别累加:模型供应商、业务租户、自然日。键形如 cost:{vendor}:{tenant}:{yyyyMMdd},字段用 Hash 存 input/output token 与估算金额。
@Service
public class CostMeter {
private final StringRedisTemplate redis;
public CostMeter(StringRedisTemplate redis) {
this.redis = redis;
}
/** 调用结束后累加本次消耗 */
public void addUsage(String vendor, String tenant,
long inputTokens, long outputTokens, double unitPriceInput, double unitPriceOutput) {
String day = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String key = "cost:" + vendor + ":" + tenant + ":" + day;
double cost = inputTokens * unitPriceInput + outputTokens * unitPriceOutput;
redis.opsForHash().increment(key, "input_tokens", inputTokens);
redis.opsForHash().increment(key, "output_tokens", outputTokens);
redis.opsForHash().increment(key, "cost_cents", Math.round(cost * 100));
// 自然日窗口,次日零点自动失效,避免键无限堆积
redis.expire(key, Duration.ofDays(2));
}
}
2.2 时间窗口与多维聚合
除按天统计外,评测平台常需”本月累计””本小时速率”两个视角。小时级键 cost:{vendor}:{tenant}:{yyyyMMddHH} 配合 TTL 实现滚动窗口;跨维度汇总直接读取各 Hash 字段做加法,无需扫描全量。
三、预算预警的触发链路
3.1 阈值规则配置
预算本身也是 Redis 里的一条配置 Hash,区分”软告警”与”硬熔断”两档,避免一次抖动就误杀任务。
| 规则字段 | 含义 | 触发动作 |
|---|---|---|
| soft_limit | 日预算 80% | 推送告警,任务继续 |
| hard_limit | 日预算 100% | 推送告警,暂停新任务 |
| cooldown | 告警冷却 5 分钟 | 防止告警风暴 |
3.2 基于 Pub/Sub 的告警推送
计完用量立即比对阈值,越界就向 alerts:cost 频道发消息,订阅方(通知服务、前端看板)各自消费,实现”计量”与”通知”解耦。
- 在
addUsage末尾读取当日累计cost_cents; - 取该租户的
soft_limit/hard_limit配置; - 累计超过 soft 且距上次告警已过 cooldown,发布预警消息;
- 累计超过 hard,发布熔断消息并标记租户状态为
SUSPENDED; - 通知服务订阅频道,按 severity 分流到企业微信 / 邮件 / 看板。
private void checkBudget(String vendor, String tenant, String dayKey) {
Long costCents = redis.opsForHash().increment(dayKey, "cost_cents", 0);
Long soft = redis.opsForHash().get("budget:" + tenant, "soft_limit");
Long hard = redis.opsForHash().get("budget:" + tenant, "hard_limit");
if (costCents != null && hard != null && costCents >= hard) {
redis.convertAndSend("alerts:cost",
"{\"tenant\":\"" + tenant + "\",\"level\":\"HARD\",\"cost\":" + costCents + "}");
redis.opsForValue().set("tenant:status:" + tenant, "SUSPENDED");
}
}
Redis Pub/Sub 适合”即时通知”这类可丢失的信号;若告警必须不丢,应改用 Redis Streams 做持久化消费组,断线重连后可回放未读消息。
四、Spring Boot 配置与生产注意点
引入 spring-boot-starter-data-redis,连接池与序列化按以下方式配置,避免大对象阻塞与乱码:
spring:
redis:
host: ${REDIS_HOST:127.0.0.1}
port: 6379
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
@Configuration
public class RedisConfig {
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory f) {
StringRedisTemplate t = new StringRedisTemplate(f);
t.setKeySerializer(RedisSerializer.string());
t.setValueSerializer(RedisSerializer.string());
return t;
}
}
评测任务量突增时,单实例计数键会瞬时高频写入。把键按租户哈希分片到不同 Redis 槽位(集群模式)可均摊写压力。金额类数据走 AOF 持久化,防止实例宕机丢失当日已计量记录。
五、用量聚合与多租户隔离
实时计数解决”当下花了多少”,运营还需要”按租户、按模型、按天”的汇总视角。由于每日键按 cost:{vendor}:{tenant}:{day} 分层,聚合只需按前缀 cost:* 用 SCAN 遍历再按字段相加,无需独立汇总表。
多租户场景下,预算要按租户独立配置,不能共用一个总额度。每个租户一条 budget:{tenant} Hash,包含 soft_limit、hard_limit、owner 通知人。评测任务入队前先查该租户状态:若为 SUSPENDED 直接拒绝,从源头卡住超预算调用。这样计费与预算都在 Redis 内闭环,数据库只承担最终落账与审计。
| 聚合维度 | 键前缀 | 典型用途 |
|---|---|---|
| 租户 × 日 | cost:*:{tenant}:{day} |
日预算核对 |
| 模型 × 日 | cost:{vendor}:*:{day} |
模型成本排行 |
| 租户 × 月 | cost:*:{tenant}:{yyyyMM} |
月度对账 |
聚合结果应定期(如每小时)回写一张 MySQL 汇总表,既降低实时查询压力,也补足 Redis 作为缓存可能丢失的审计链路。Redis 负责”快”,数据库负责”准”,二者分工明确。
六、告警通道与降级
告警消息本身不应成为单点。订阅 alerts:cost 的通知服务把消息转成企业微信、邮件与看板三类出口;若 Redis 连接断开,计量模块降级为”只计数不告警”,并在恢复后由定时任务补扫超预算租户,保证最终一致。告警文案携带租户、累计金额、阈值三要素,运维无需进库即可判断严重程度,先停任务再查原因。冷启动阶段建议把阈值先放宽一档观察一周,摸清真实用量曲线后再收紧,避免上线初期误伤正常评测。
常见问题(FAQ)
Q1:计数键过期会丢失当日预算吗?
不会。TTL 设为 2 天,统计与告警在当日内完成,次日才清理。
Q2:Pub/Sub 消息丢失怎么办?
告警必须可达时改用 Redis Streams,消费组可回放未确认消息。
Q3:多实例并发计数会超量吗?
不会。INCRBY 在 Redis 单线程内原子执行,无需应用层加锁。