题目重点:围绕题目明确追问的幂等键,设计请求指纹、同键并发锁定、原子分录、未知结果恢复及日终对账。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版 API 为某账户间同币种转账,金额为正整数最小单位,每次成功业务操作生成平衡的 debit/credit 两条分录。具体产品资金流与 settlement 过程题目未公开。
- 调用方在一个明确命名空间内提交唯一 idempotency key,同键且同请求指纹返回首次提交结果;同键不同请求拒绝。键的保留期和资源归属由真实业务合同决定。
- 外部处理可能在客户端超时后已成功,故超时不是失败证明;ledger durable commit 与幂等记录必须在同一原子事务或同等一致性协议中完成。
解题路线
- 设计 IdempotencyRecord(scope,key,request_hash,status,response,ledger_tx_id,created_at),唯一索引约束 (scope,key)。对新 key 插入 PROCESSING 占位并取得排他权;同键并发请求等待/返回处理中,再读取已存终态。
- 在同一事务内校验资金与版本、写借贷平衡分录、业务对象状态和幂等终态响应;任何阶段失败回滚。返回网络响应丢失时,重试从幂等记录取原始结果而不是再执行转账。
- 对于调用第三方的外部支付步骤,单个数据库事务无法覆盖远端。以状态机/outbox 记录外部调用 ID,重试相同远端幂等键;超时先查询对端状态或走对账,不把‘未知’自动补一笔新款。
- balance 是 ledger 聚合/物化视图,更新必须与分录有明确一致性路径。对账按外部对账单、内部业务操作 ID、ledger 分录和余额视图逐层核对;异常按少分录、重复分录、延迟 settlement 分类。
- 测试同键同时请求、同键异 payload、提交后回包丢失、数据库崩溃在每个写点、远端成功但本地超时、对账多/少一笔与退款引用原始操作。
手动走一遍
训练例两个线程带同 key K 和相同金额 500 同时到达,唯一索引/锁只让一个线程执行;最终只有一组借贷各 500 的分录。第一个提交后响应在网络上丢失,第二个重试拿到原先的成功响应。若 K 被不同金额 600 重用,则返回冲突而不更新账本。
规模与取舍
不是单一算法。一次操作通常 O(1) 索引查写与固定数分录,存储随成功业务操作数 T 增长 O(T);并发同键延迟受唯一索引争用/事务锁决定。日终对账可按有序操作 ID 流式 merge O(T+E) 于内部和外部记录数。
易错点与追问
容易失分的边界
- 只在进程内 map 去重,重启或多实例后失效。
- 先提交 ledger 后另事务写幂等记录,崩溃窗口可重复入账。
- 同键不同请求仍重放旧成功,不告诉调用方参数冲突。
- 客户端超时就假定失败并换 key 重发。
- 把 ledger 与余额都当独立真值,却没有对账和修复链。
继续深挖
- 幂等键 TTL 到期后,旧请求重试可能重复操作,如何定义保留策略与永久业务 ID?
- 跨币种 FX 报价过期后同键重试,应重放原 quote 还是重新定价?
- 一次操作跨多个账务 shard 时如何保证借贷平衡与故障恢复?
