什么是幂等性?如何处理重复的 webhook?

幂等性使重复操作产生与单次操作相同的效果,因此处理重复 webhook 时应避免重复执行其工作。

连接中断可能让你无法确定发送请求是否成功。丢失的确认也可能导致 webhook 发送方投递一个你的应用已经存储过的事件。

这两种故障发生在相反的方向。Bird 可以通过你提供的键识别重复的 API 请求。你的 webhook 接收端需要自行记录已接受的事件。

如何安全地重试发送?

每次重试都复用相同的 Idempotency-Key 和完全相同的请求。该保证是只接受一次:三小时内匹配的重试必须归结为同一个被接受的操作,即使你丢失了它的响应。

你自行选择键,最多 255 个字符,并在重试时保持不变。使用稳定的值(如 welcome-user/usr_abc123)可以在进程重启后仍能标识同一条欢迎消息操作。

该请求头适用于变更类请求,如 POST、PATCH 和 DELETE。不带键的请求会跳过此去重机制。GET 忽略该请求头,因为读取资源本身就可安全重复。

Bird 会为匹配的已完成请求返回存储的响应,包括其原始状态码和响应体。响应中携带 Idempotency-Replay: true,方便你在日志中识别该复用。

幂等性指南记录了已完成响应窗口的默认时长为三小时。窗口过期后的重试会作为新操作执行。不要依赖该键来永久防止重复发送。

Bird SDK 会为变更操作生成键,并在内部重试中复用。Bird CLI 也会在缺少键时为变更请求生成键。当多次独立的命令调用需要共享同一操作时,请显式设置 --idempotency-key。

对于 SMTP 提交,使用 X-Bird-Idempotency-Key 消息请求头。这样重试的提交可以标识为同一操作。

如果错误地复用了键会怎样?

Bird 会拒绝冲突的键使用,而不会返回另一个操作的响应。

场景响应与恢复方式
相同的键和请求,响应已保留在保留期内返回存储的响应,附带 Idempotency-Replay: true。
已完成的键被用于不同的请求409,附带 E01005,表示幂等键被复用。修正键后再重试。
使用该键的另一个请求仍在运行409,附带 E01004,表示请求正在处理中。稍等片刻后重试。
键超过 255 个字符在声明了该请求头的端点上返回 422,附带 E01001。缩短键的长度。

比较范围包括方法、端点、路径参数、查询字符串和请求体。对于 JSON,更改空白字符会改变请求的标识,因此在重试时应保留原始请求体。

如果原始请求仍在运行,请等待后再重试。保持相同的键和请求,这样重试才能标识原始操作。

API 不会将 5xx 响应作为已完成的响应保留。请在三小时内使用有上限的退避、相同的键和完全相同的请求重试超时或服务器错误。只接受一次的保证同样适用于丢失响应的情况。

4xx 拒绝可能会被保留。修正后的请求应使用新键:原样重试会重放已保留的拒绝,而更改输入会与该已保留的键发生冲突。

为什么我会收到相同的 webhook 两次?

如果 Bird 未收到成功响应,它可能会重试一个你的接收端已存储的事件。

接收端可能在连接断开前刚好存储了事件。Bird 没有看到成功的确认,因此会重试,即使接收端已经拥有该事件。

每次重试都保留相同的 webhook-id 请求头,用于标识该事件。失败投递的重放也保留该标识符,因此两者都可被识别为同一事件。

如何使我的处理程序具有幂等性?

在调度事件的工作之前,使用数据库唯一约束存储每个 webhook-id。

先查询是否存在再插入会留下竞态条件:两个并发请求都可能看到没有记录。让数据库来拒绝重复的标识符。

在同一事务中存储标识符和任务。这可以防止标识符被记录而没有任务被入队。

  1. 验证请求,然后在同一事务中插入其标识符和任务。
  2. 在该事务提交后返回 2xx,这样 Bird 就可以停止重试。
  3. 在一个能够安全重复自身操作的 worker 中处理已存储的任务。

对于已提交的重复标识符,返回成功而不创建另一个任务。对于失败的事务,返回错误以便 Bird 重试。

将耗时的工作移出接收端,因为等待它可能导致请求超时。Worker 可能因与 webhook 投递无关的原因而重试,因此仅保护接收端是不够的。

事件也可能乱序到达。在覆盖较新状态之前,比较 timestamp 中的事件时间。失败的 webhook 重试包含部分费用示例。

不应依赖什么?

不要假设请求去重能使重复的副作用完全不可能发生。

对于受支持的 HTTP 写入操作,Bird 会将幂等键绑定到请求所属的工作区或组织。受支持的员工操作则绑定到执行操作的员工账户。如果无法提供该保护,Bird 会在执行本次尝试之前返回 503 E01033 IdempotencyUnavailable。这并不能确定之前某次尝试的结果。恰好一次接受保证涵盖三小时内匹配的重试。请检查消息状态以确认是否已送达。如果重试次数已用完或窗口已过期,请先检查原始操作的结果,再重新提交。

同样,webhook-id 区分的是同一事件的重复投递。不同的事件有不同的标识符。你的应用仍需自行判断这些事件是否应当重复执行相同的操作。

幂等性记录了 API 的重试行为。Webhooks 涵盖了你的接收端需要处理的独立投递保证。

简而言之

  1. API 重试和 webhook 重试需要不同的记录。

    向 Bird 发送请求时复用 Idempotency-Key。你的接收端存储 webhook-id 来识别已接受过的事件。

  2. 拒绝也可能被保留。

    被保留的拒绝会被重放。修正后的请求需要使用新键,因为其输入已经不同。

  3. 已完成的键不能标识不同的请求。

    更改 JSON 的请求体或端点可能产生 409 冲突。应修正键,而非原样重试该冲突。

  4. 去重有其局限性。

    API 的保证范围是:三小时内匹配的重试只被接受一次。你的 webhook 接收端需要自行持久记录事件。

基于同一网络构建。

测试 API 密钥即刻获取。添加付款方式并验证发送者身份后,即可解锁生产环境。

Start building with Bird in minutes.