连接中断可能让你无法确定发送请求是否成功。丢失的确认也可能导致 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。
先查询是否存在再插入会留下竞态条件:两个并发请求都可能看到没有记录。让数据库来拒绝重复的标识符。
在同一事务中存储标识符和任务。这可以防止标识符被记录而没有任务被入队。
- 验证请求,然后在同一事务中插入其标识符和任务。
- 在该事务提交后返回
2xx,这样 Bird 就可以停止重试。 - 在一个能够安全重复自身操作的 worker 中处理已存储的任务。
对于已提交的重复标识符,返回成功而不创建另一个任务。对于失败的事务,返回错误以便 Bird 重试。
将耗时的工作移出接收端,因为等待它可能导致请求超时。Worker 可能因与 webhook 投递无关的原因而重试,因此仅保护接收端是不够的。
事件也可能乱序到达。在覆盖较新状态之前,比较 timestamp 中的事件时间。失败的 webhook 重试包含部分费用示例。
不应依赖什么?
不要假设请求去重能使重复的副作用完全不可能发生。
对于受支持的 HTTP 写入操作,Bird 会将幂等键绑定到请求所属的工作区或组织。受支持的员工操作则绑定到执行操作的员工账户。如果无法提供该保护,Bird 会在执行本次尝试之前返回 503 E01033 IdempotencyUnavailable。这并不能确定之前某次尝试的结果。恰好一次接受保证涵盖三小时内匹配的重试。请检查消息状态以确认是否已送达。如果重试次数已用完或窗口已过期,请先检查原始操作的结果,再重新提交。
同样,webhook-id 区分的是同一事件的重复投递。不同的事件有不同的标识符。你的应用仍需自行判断这些事件是否应当重复执行相同的操作。
幂等性记录了 API 的重试行为。Webhooks 涵盖了你的接收端需要处理的独立投递保证。
简而言之
API 重试和 webhook 重试需要不同的记录。
向 Bird 发送请求时复用 Idempotency-Key。你的接收端存储 webhook-id 来识别已接受过的事件。
拒绝也可能被保留。
被保留的拒绝会被重放。修正后的请求需要使用新键,因为其输入已经不同。
已完成的键不能标识不同的请求。
更改 JSON 的请求体或端点可能产生 409 冲突。应修正键,而非原样重试该冲突。
去重有其局限性。
API 的保证范围是:三小时内匹配的重试只被接受一次。你的 webhook 接收端需要自行持久记录事件。