生图API 怎么避免重复计费?加一层结果缓存和请求去重(nano-banana-pro 实践)

有个月对账,我发现出图次数比预期高了三成。查下来原因很蠢:同样的提示词被重复调用了。

重复是怎么产生的

三个来源,都很常见:

一、用户手抖。 生成按钮点了没反应(其实在跑),用户又点一次,两个一模一样的请求同时打出去。

二、重试打偏。 网络超时触发重试,但服务端其实已经生成成功了,只是响应没回来。重试等于又生成一张。

三、批量任务重跑。 跑到一半挂了,重跑的时候没有断点,前面成功的那些又生成了一遍。

三种加起来,浪费的比例相当可观。

解法:一个 key 管两件事

给每次请求算一个内容指纹,用它同时做缓存和去重。

const crypto = require('crypto');

function fingerprint({ model, prompt, aspect, size, refImage }) {
  return crypto.createHash('sha256')
    .update(JSON.stringify({ model, prompt, aspect, size, refImage: refImage || '' }))
    .digest('hex');
}

关键是把所有影响出图结果的参数都算进去。漏掉 aspect_ratio 的话,同一句提示词出方图和长图会撞成同一个 key,取到错的缓存。

缓存 + 在途去重

const cache = new Map();      // fp -> 结果
const inflight = new Map();   // fp -> 正在跑的 Promise

async function genImage(params) {
  const fp = fingerprint(params);

  // 1) 已经生成过,直接返回
  if (cache.has(fp)) return cache.get(fp);

  // 2) 正在生成中,复用同一个 Promise —— 这一步挡住了「用户连点两次」
  if (inflight.has(fp)) return inflight.get(fp);

  const p = callGenApi(params)
    .then((r) => { cache.set(fp, r); return r; })
    .finally(() => inflight.delete(fp));

  inflight.set(fp, p);
  return p;
}

inflight 这层比缓存还重要。 缓存只能挡住"已经跑完的重复",而用户连点、并发重试制造的是"同时发出的重复"——那时候缓存里什么都没有,只有在途表能挡住。

什么时候不该走缓存

得留一个绕过缓存的口子,否则用户会困惑:"我就是想换一张,为什么每次都给我同一张。"

async function genImage(params, { noCache = false } = {}) {
  if (noCache) return callGenApi(params);
  // ...
}

界面上的「重新生成」按钮走 noCache: true,正常路径走缓存。

持久化

内存 Map 重启就没了,批量任务重跑的场景挡不住。生产上建议落到 Redis 或数据库:

  • key:指纹
  • value:图片地址 + 生成时间
  • TTL:按图床的链接有效期定,别缓存得比链接还久

该服务返回的是图片链接,如果你把图转存到了自己的存储,缓存里存自己的地址,就不用担心过期问题。

上线之后

重复调用基本归零,那三成的额外消耗省下来了。

顺带的好处是用户体验变好了:连点两次不会等两倍时间,第二次直接复用第一次的结果,秒返回。

这类优化投入很小——核心逻辑就上面那二十来行——但账单和体验都能立刻看到变化,属于性价比很高的一类活。


接口服务:甜甜圈API(dashengfenshen.cn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值