genmux API 现已全面开放。 查看文档 →
genmux API

博客

图像 API 的幂等键与恰好一次计费

1 分钟阅读作者 genmux API Team


每一个按量计费的 API 都有同一个噩梦场景:客户端超时、重试,然后同一份工作被扣了两次钱。或者反过来——服务商在调用中途挂掉,客户花钱什么也没拿到。本文说明我们给出的保证、它是怎么实现的,以及它能让你从自己的代码里删掉什么。

我们的保证

一笔积分最多只对应一张已交付的图片。 具体来说:

  • 提交时原子地预留任务费用(一次条件式单行更新——余额永远不会变成负数,两个并发提交也不可能都花掉最后那点积分)。
  • 失败或被取消的任务会自动退回预留。流水表在 (任务, 类型) 上唯一,因此退款本身也是恰好一次的,即便两个 worker 同时尝试。
  • 对上游模型的调用是写前记录的:在调用供应商之前我们先记下这次尝试;如果进程在调用中途死掉,对账会拿这条记录去和供应商核对,而不是靠猜。任何状态未知的调用都不会被悄悄计费。

幂等键补上客户端那一半

如果客户端的重试会创建出第二个任务,服务端的保证就毫无意义。这正是那个必填的 Idempotency-Key 请求头存在的理由:

  • 同一个 key + 同一个请求体 → 返回原来那个任务(200,不是 202)。永远不会产生新的扣费。
  • 同一个 key + 不同的请求体 → 422 idempotency_key_mismatch,因为悄悄返回旧任务会把你代码里的 bug 藏起来。
  • key 的记忆时长足以覆盖任何现实中的重试窗口。

实践规则:在用户意图产生的地方生成这个 key(「用户点了一次生成」对应一个 key),而不是每次 HTTP 尝试生成一个。然后你的 HTTP 层想多激进地重试都可以。

text
意图 → key K
  第 1 次尝试 (K) — 网络超时,你根本没看到响应
  第 2 次尝试 (K) — 200,返回第 1 次尝试创建的那个任务

你可以从代码里删掉什么

  • 拿你的数据库和我们对账的定时任务:退款流水在构造上就保证了 sum(扣费) == sum(已交付图片) × 单价。
  • 「重试是不是把我们扣了两次?」这类告警——用同一个 key 的重试不可能做到。
  • 每次提交前的防御性余额检查:402 insufficient_credits 出现时直接处理就行,不会有部分扣费的情况。

仍然需要处理的

  • failed 任务:钱退了,但你的界面还是应该把 error.code(例如 content_policy)呈现出来。
  • 429:按 Retry-After 退避;被拒绝的请求压根不会产生预留。
  • 预签名 URL 过期(15 分钟):重新 GET 一次任务拿新 URL——免费。

完整行为记录在 API 参考里;如果你在对比服务商,不妨问问每一家:当一个任务在他们的队列和模型之间死掉时,你的钱会怎么样。这个问题能把值得信任的按量计费 API,和需要你每月人工审计的那些区分开来。

关于作者

作者 genmux API Team — genmux API.

了解我们