重试把作品发了三遍:幂等键到底该怎么设计
一、重试放大了重复
上传超时会重试,这本身没错。错在每次重试都重新生成随机 UUID 作为请求标识——在服务端看来,这是三个不同的请求,于是老老实实创建了三条记录,作品列表里出现三份同名条目,还得手工删。
幂等键的核心是:同一个业务意图,必须产生同一个键。随机数不满足这一点,它只能区分请求,不能识别意图。UUID 适合做日志关联 ID,不适合做幂等键。
二、键该由什么组成
推荐的构成是「主体 + 业务唯一标识 + 内容指纹」。主体是账号或租户 ID,业务唯一标识可以是种子、业务单据号,内容指纹取关键字段的哈希。三者拼起来,既不会撞,也能保证重试稳定。
如果业务本身有天然唯一键(订单号、工单号),直接用它,别自己发明。没有的话,用「主体 + 时间粒度 + 序号」也行,但要注意时钟回拨:用本地时间做粒度,机器时间跳变会造出重复键,保险做法是带上一个自增序号或状态机的步骤名。
三、服务端怎么用这个键
常见实现是存一张幂等表:键 + 状态 + 首次响应体 + 过期时间。收到请求先查表,命中且已完成就把首次响应原样返回(连 HTTP 状态码一起复用);命中但处理中就返回 409,或者让客户端轮询同一个键直到状态落定。
过期时间要大于客户端最大重试窗口,通常是业务超时的数倍。设太短,长尾重试会穿透;设太长,表会膨胀。落地时给键加唯一索引,用数据库的唯一约束兜住并发,别只靠「先查再写」——两个请求同时查空、同时写入,照样重复。
四、客户端要注意什么
第一,键要在发起第一次请求之前就生成并持久化,进程崩溃重启后还能复用同一个键,否则重试链路一断就退化成新建。第二,重试只针对可重试错误(超时、5xx、429),4xx 参数错误重试没有意义,只会把日志刷满。
还有一条最容易忽略:重试时请求体必须完全一致。如果中间改了字段却复用旧键,服务端收到的就是「同一个键、不同内容」,行为完全取决于实现,可能报冲突,也可能静默用第一次的旧数据覆盖你的新数据。
五、退避和超时要一起调
只加退避不改超时,等于把重试窗口拉长到网关的容忍边界之外。实测里网关读 body 大约 30 秒就断,客户端超时设 180 秒,第一次请求还在等,重试逻辑根本没机会触发。
合理的组合是:客户端超时略大于服务端预期处理时间(比如 60 秒),退避 1s/2s/4s 加抖动,总预算不超过网关上限的一半。顺序也要定死:先判断错误类型,再决定是否退避,最后才复用幂等键重发。
六、验收方法
最简单的验收是人为制造超时:把上传超时阈值调到 1 秒,跑一次上传,确认平台上只有一条记录。再人为让第一次请求成功但响应丢失(比如代理丢响应体),确认客户端拿到的是幂等返回而不是报错。
这两个场景覆盖了绝大多数线上事故,值得写进回归用例。顺带记一条:验收时要看「列表条数」,不是看「接口是否 200」——重复创建同样是 200 或 201,只有条数能暴露问题。
- 点赞
- 收藏
- 关注作者
评论(0)