题干与适用场景
支付、工单和资源创建 API 会遇到请求已执行但响应丢失的情况。客户希望用 Idempotency-Key 安全重试。请决定是否推出该能力,并说明承诺边界、优先客户、指标、成本和失败回滚。
面试官考察点
考察你能否把协议能力转成可验证产品契约:哪些操作支持、键的生命周期、参数不一致如何处理、重复请求如何返回,以及如何平衡可靠性、存储成本和开发者体验。
回答前需要澄清的问题
先确认客户最痛的副作用是重复扣款、重复资源还是重复通知;再问请求规模、重试窗口、跨区域要求和合规留存。还要区分“服务端去重”与业务事务真正成功,避免把网络重试承诺扩大成 exactly-once。
30 秒回答
“我会先针对高风险写操作推出有边界的幂等键,而不是承诺所有接口 exactly-once。契约应规定键格式、租户作用域、保存窗口、参数指纹和重复响应;同一键不同参数必须报错。先用支付和资源创建客户做试点,跟踪重复副作用率、成功重试率、存储成本和支持工单,再以灰度和清晰的 409/422 语义扩大范围。”
分步骤深入解答
定义用户价值
把“超时后敢重试”作为核心结果,优先有财务或资源副作用的 POST。只读请求和天然幂等的 PUT 不应为了营销而重复建设。
写清契约边界
规定键在租户内唯一、长度上限、保存窗口和可重试状态。服务端保存首次请求的状态码与响应体;同键不同参数返回冲突,避免静默复用错误结果。
连接业务事务
去重记录必须与副作用提交处在同一可靠事务或具备可恢复 outbox。缓存命中不能证明下游支付已结算;异步任务要返回可查询的 operation id。
设计错误与兼容
区分进行中、已成功、已失败和已过期。客户端 SDK、文档和网关要一致传递键;旧客户端仍可工作,但不能获得重复保护的隐含承诺。
选择指标与成本
核心指标是重复副作用率和安全重试成功率,护栏包括键存储容量、P95 延迟、冲突率和支持量。按租户和风险等级估算 TTL 与存储,而非无限保存响应。
分阶段发布
先在单区域、支付沙箱和内部 SDK 开启,使用影子去重日志验证命中率,再让少数租户 opt-in。出现响应不一致、存储失控或下游不支持事务时,关闭新入口但保留旧请求路径。
高质量示范回答
我会把幂等键定位为高风险写操作的重试安全契约。先选择支付和资源创建,规定租户作用域、键格式、保存窗口、参数指纹和重复响应;同键不同参数报冲突,异步副作用返回可查询 operation id。指标同时看重复副作用率、安全重试成功率、P95 延迟、冲突率和存储成本,先沙箱与 opt-in 灰度,事务或下游能力不足时不扩大承诺。
常见错误
承诺 exactly-once
幂等键主要消除客户端重复提交,不能覆盖下游不可恢复的副作用;应明确 at-most-once 处理与最终状态查询。
无限保存键
永久保存会带来成本、隐私和清理问题;按风险定义 TTL,并记录清理后的过期语义。
忽略参数变化
同键不同请求若返回第一次结果,会隐藏客户端 bug;应保存参数指纹并返回冲突。
只做网关缓存
网关缓存与业务事务可能分离;关键去重记录要和副作用有可靠一致性或可恢复补偿。
追问及应对
为什么不让所有 POST 都支持?
不同写操作的副作用和成本不同;先覆盖高风险场景,避免对低价值接口制造复杂契约。
重试时第一次请求仍在处理中怎么办?
返回明确的进行中状态和 operation id,让客户端轮询或订阅,不并发执行第二个副作用。
多区域如何保证键一致?
先限定单主区域或按租户分片;跨区域需要一致的去重存储与故障演练,不能只复制缓存。
失败响应能否重用?
契约应区分可重试的暂时失败与已确定失败,并说明是否缓存状态码和响应体;Stripe 的实践会保存首次结果,因此需让客户理解 TTL 和错误语义。