← 中文资源

简体中文版本

端到端测试幂等键

重放同一个幂等键,断言只产生一次副作用、返回相同响应,并在请求体变化时返回冲突。

阅读英文原文

概述

重试不可避免:网络丢失响应、移动端超时、网关自动重发。从客户端看,超时的请求处于未知状态,可能已经成功。没有幂等机制时,“安全的重试”与“安全的业务”相互冲突:重试可能重复扣款。幂等键让服务端识别重试并返回原始结果,从而化解这个冲突。

步骤

  1. 建立带唯一约束的幂等键表:
CREATE TABLE idempotency_keys (
  tenant_id uuid NOT NULL,
  key text NOT NULL,
  request_hash text NOT NULL,
  state text NOT NULL,
  response_status int,
  response_body jsonb,
  PRIMARY KEY (tenant_id, key)
);
  1. 在与副作用相同的事务中先声明键,再执行扣款:
INSERT INTO idempotency_keys (tenant_id, key, request_hash, state)
VALUES ($1, $2, $3, 'processing')
ON CONFLICT (tenant_id, key) DO NOTHING;
  1. 完成业务后把状态码与响应体写回同一行,提交事务。
  2. 相同键但请求体不同时返回 422 与 idempotency_key_reuse,而不是返回旧结果。
  3. 并发发送 5 个相同请求,断言只产生一条扣款记录。

验证

顺序重放同一键应返回 201 与相同的扣款 id,扣款表中只有一行。相同键不同金额应返回 422。5 个并发请求中,成功响应都指向同一个 id,其余请求在 processing 期间返回 409,最终 countCharges('k-2') 为 1。

注意事项

  • 先写键再做业务,否则崩溃窗口会留下无键的扣款,重试将产生第二笔。
  • 并发重复请求依赖 ON CONFLICT DO NOTHING 的 rowCount 或 SELECT FOR UPDATE 才能只放行一个。
  • 必须持久化状态码、响应体与影响客户端行为的响应头,重放不能重新计算。
  • 键应按租户与接口隔离,避免不同操作复用同一字符串。
  • 键需要保留窗口,过期后重放可能重复;清理任务应可观测。