重试之前,先问一句:它幂等吗

举报
yd_251844643 发表于 2026/10/30 03:14:12 2026/10/30
【摘要】 重试是最常用的容错手段,但它在放大一个隐含假设——调用两次和调用一次效果一样。假设不成立时,重试不是容错,是制造故障。本文拆解三类操作的不同命运,并给出三种把不幂等操作变幂等的落地方法。

接口偶发超时,第一反应是什么?大多数人会写:加个重试。重试确实是分布式系统里最常用的救命手段,但它也是最容易被滥用的。重试一把梭的代码,平时安静得像什么都没发生,出事的时候能把整个系统拖下水。

问题不在于「要不要重试」,而在于很多人跳过了一个更根本的前置问题:被重试的那个操作,幂等吗?

一次真实事故

我见过一个下单接口,客户端 3 秒超时后自动重试。某天数据库抖动,响应慢到了 4 秒。结果就是:第一笔请求其实成功了,只是响应没来得及返回;客户端超时重试,又下了一单。用户付了一笔钱,拿到两份货。

事后复盘,所有人都盯着「为什么数据库会抖」,但真正的问题是:这个接口从设计的第一天起,就没打算被安全地调用两次。

重试永远在放大一个隐含假设——「调用两次和调用一次效果一样」。这个假设有个名字,叫幂等。假设不成立的时候,重试不是容错,是制造故障。

三类操作,三种命运

天然幂等的。查询接口,调一百次结果都一样。删指定 ID 的资源,第二次调用返回「不存在」但状态一致。这类放心重试,基本不用想。

天然不幂等的。扣款、插入带自增主键的记录、发一条消息、计数器加一。这类每调一次就产生一次真实效果,重试前必须先解决「怎么防止重复生效」。

看起来幂等其实不幂等的。最阴险的一类。比如「把状态更新为已支付」看似幂等,但如果状态流转上还挂了副作用——发通知、加积分、触发回调——那么两次成功调用就会触发两轮副作用。表面状态一致,背后账全乱了。

让不幂等的操作变幂等

业界成熟的解法其实就一句话:让重复请求可以被识别,然后被丢弃。

幂等键(Idempotency Key)。调用方为每个业务操作生成唯一 ID,随请求带上。服务端维护一张键表:第一次见到就执行并记录,第二次见到直接返回上次的结果。支付系统普遍用这套, stripe 的 API 设计是教科书式的范例。

业务唯一约束。用业务本身的天然唯一性做防线:订单号唯一索引、「同一用户同一商品同一秒只允许一单」的唯一键。重复请求进来,数据库层直接拒绝,最硬也最可靠。

条件更新。把「更新状态」改成带前置条件的更新:UPDATE orders SET status='paid' WHERE id=? AND status='unpaid'。第二次执行影响行数为零,副作用代码自然就不会跑。这是我最偏好的方案——不需要额外存储,语义还精确。

配套的两个习惯

重试必须有边界。次数上限、指数退避、超时熔断,三样缺一不可。没有退避的重试在故障期间等于 DDoS 自己:服务越慢,重试越密,越重试越慢。

重试范围要收窄。不要把整个流程包在一个大重试里。调用十个下游的重试策略应该各自独立——查询类可以激进重试,写入类必须先确认幂等再重试。一把梭的统一重试策略,等于把最脆弱的那个下游的风险平摊给了所有人。

写在最后

重试和幂等的关系,本质上是「失败恢复」和「重复防护」的关系。前者是乐观的,相信第二次会成功;后者是悲观的,防着第二次本来就是第一次的回声。

下一次往代码里加重试之前,先停下来问一句:这个操作被调用两次,会发生什么?答案如果是「再扣一次钱」,那你要写的第一行代码不是重试,而是幂等。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。