← 返回首页

PHP 接口幂等不能只靠一张表:从重试语义到分布式一致性

2025-06-17 · PHP 后端 / 分布式

很多人做接口幂等,第一反应是“建一张幂等表,拿请求号去重”。这当然是必要动作,但远远不够。因为真正的问题不在“有没有重复请求”,而在“系统如何定义同一次业务意图,以及这个意图在多步执行中如何保持一致”。

先定义什么叫“同一次请求”

客户端重试、网关超时重放、消息队列重复投递,本质上都不是同一个技术问题,但业务上常常要被视为同一次意图。幂等键必须绑定业务语义,而不是绑定 transport 层细节。比如支付创建,幂等键更适合由商户单号和动作类型推导,而不是简单拿 HTTP request id 顶上。

幂等表只解决“看见过”,不解决“执行到哪”

真正麻烦的是中间态:数据库已写,MQ 未发;外部接口已调用,事务未提交;状态已推进,但响应还没返回。此时即使幂等表记录了“处理过”,也未必知道该不该重试、重试到哪一步、是否要补偿。因此幂等要和业务状态机一起设计,而不是孤立成一个拦截器。

把“结果缓存”与“执行锁”区分开

很多实现把 Redis setnx、数据库唯一键、响应缓存混成一层。更清晰的拆法是:执行锁负责限制并发进入,结果缓存负责对重复请求返回同一业务结果,状态机负责判断中途失败后的恢复路径。三者职责不同,不能互相代替。

// 伪代码:先拿幂等记录,再根据状态推进
if ($record = $repo->findByKey($idempotencyKey)) {
    return $record->toResponse();
}

$lock->acquire($idempotencyKey);
try {
    $state = $service->execute($command);
    $repo->storeResult($idempotencyKey, $state);
} finally {
    $lock->release($idempotencyKey);
}

结论

幂等从来不是“防重”这么简单。它真正要回答的是:当世界不可靠、调用会重试、消息会重复时,系统能否把同一业务意图收敛到一个稳定结果。只靠一张表,通常不够。