生图API 的钱怎么不超支?日预算加三层告警

有个月账单比平时高了不少。查下来是个循环写错了——重试逻辑没有上限,一个失败的任务反复重试了几百次。 问题不在于那次错误,**在于它跑了三天我才发现**。 ## 需要三层防护 事后查账没有意义,要能在**跑的过程中**拦住。我们后来加了三层: | 层 | 作用 | 触发时机 | | --- | --- | --- | | 速率异常检测 | 抓突发 | 分钟级 | | 日预算 | 抓累积超支 | 实时 | | 硬熔断 | 兜底 | 超过日预算 N 倍 | ## 第一层:速率异常 正常业务的调用速率是有规律的。突然翻十倍,八成是 bug 不是业务爆发。 ```js // 每分钟统计一次,和过去一小时的均值比 async function checkRateAnomaly() { const lastMin = await db.usage.countSince(Date.now() - 60_000); const hourAvg = (await db.usage.countSince(Date.now() - 3600_000)) / 60; // 至少要有基数,否则从 1 涨到 5 也会报警 if (lastMin > 10 && lastMin > hourAvg * 5) { alert(`生图调用速率异常:本分钟 ${lastMin} 次,小时均值 ${hourAvg.toFixed(1)} 次/分`); } } ``` **`lastMin > 10` 这个下限很重要**,不然半夜没量的时候一点波动就报警,报几次之后大家就开始忽略告警了——那比没有告警更糟。 ## 第二层:日预算 按 units 算,不是按次数: ```js const COST = { 'nano-banana-pro': 3, 'nano-banana2': 1, 'gpt-image-2': 2 }; async function guardBudget(model) { const used = await redis.get('budget:' + today()) || 0; const add = COST[model] || 1; if (Number(used) + add > DAILY_BUDGET) { throw Object.assign(new Error('今日预算已用完'), { code: 'BUDGET_EXCEEDED' }); } await redis.incrby('budget:' + today(), add); } ``` 放在调用之前。注意**预算要在调用前扣**,调用后扣的话并发场景下会超支——十个请求同时通过检查,然后十个一起扣。 ## 第三层:硬熔断 前两层都是可能被绕过的(比如有人临时把预算调大)。最后留一道死线: ```js if (Number(used) > DAILY_BUDGET * 3) { await redis.set('gen:circuit_open', '1', 'EX', 3600); alert('生图调用已熔断一小时,请人工确认'); } ``` 熔断之后一小时内所有生图请求直接拒绝。这一层的意义是:**哪怕所有人都睡着了,损失也有上限。** ## 告警发给谁、发什么 告警的内容比有没有告警更重要。我们的告警里必带四样: - 触发的是哪一层 - 当前用量 / 阈值 - **消耗最多的前三个调用方**(租户或业务模块) - 最近一次调用的 traceId 有了后两样,收到告警的人**能直接定位到是谁在刷**,而不是收到一句"用量超了"然后开始翻日志。 ## 一条反直觉的经验 **别把预算设得太贴近实际用量。** 我第一版把日预算设成了历史峰值的 1.1 倍,结果一次正常的大促活动就触发了熔断,业务直接中断。 现在的设法是:**日预算设成正常峰值的 2~3 倍**(挡住量级错误,不挡业务波动),**速率异常告警设得敏感一些**(早发现,但只通知不拦截)。 拦截要宽,告警要严。反过来的话,要么拦不住 bug,要么天天误伤业务。 **甜甜圈API**(dashengfenshen.cn)本身单价不高,正常业务用量下这套防护基本不会触发。它防的是**代码 bug 造成的失控**——而这类问题,往往就是在你最没防备的时候发生的。 --- 接口服务:甜甜圈API(dashengfenshen.cn)
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容