你可能遇到过这种场景:
订单接口偶尔超时,本来只想加个重试兜底,结果同一笔订单下了两三单
消息投递怕丢,加了多次重试,用户的通知和优惠券收到了两份
日志里看起来都是超时后重试成功,业务侧却一边忙着退款,一边手动去重
很多团队以为自己在提升可靠性,实际上是在把问题从偶发失败,变成高频重复。
问题不在重试这个动作,而在于没有区分能重试的东西,没有设计幂等边界,也没有按错误类型拆策略。
这篇文章解决三件事
一是搞清楚重试为什么会把单次错误放大成重复请求
二是订单和消息这种场景下幂等和去重应该怎么落地
三是重试策略该怎么按错误类型拆开,让系统既能救急又不制造新坑
================================
一、现象:错误没少,重复反而变多了
1、接口层看着正常,业务数据一团糟
常见表现包括:
- 支付或下单接口偶尔超时,调用方启动重试,最后同一笔业务生成多条订单
- 消息系统偶发网络抖动,多次重发同一条消息,用户手机连续跳相同通知
- 后台统计时发现订单数和消息数偏高,和实际成交与实际推送记录对不上
技术侧只看到接口成功率提升
业务侧只看到重复扣款、重复发货、重复通知
2、日志只记成功,不记成功了几次
另一个隐性问题是观测维度太粗:
- 监控只看接口成功率和平均延迟
- 同一笔业务可能被执行多次,却没有任何指标反映
- 重试一加,成功率确实变高了,重复率也跟着往上窜
久而久之,系统在监控数据上越来越好看,实际体验却越来越怪。
================================
二、根本原因:没有幂等边界的重试天然制造重复
1、把多次调用当成一次业务
多数重试逻辑默认了一个危险前提:
上一次调用没成功,所以这次补一次就好。
现实网络里,超时只能说明客户端没收到结果,并不能说明服务端没处理:
- 第一次请求已经在服务端写入了订单
- 客户端等不到返回,以为失败,开始第二次重试
- 服务端再次处理,生成另一条合法订单,两条都看起来正常
没有业务侧幂等设计,底层根本分不清哪条是多余的。
2、缺少业务级唯一标识
不少系统只有技术层的请求编号,业务层却没有真正的一笔业务号:
- 重试时重新生成请求标识
- 底层把每次请求都当成全新的业务
- 上游觉得是重试,下游只看到多条独立请求
没有业务级唯一键
任何重试其实都是在默许重复执行。

================================
三、设计思路:先让业务幂等,再谈怎么重试
1、给订单和消息加业务幂等键
对订单、支付、消息这种关键场景,至少要有一个稳定的业务幂等键:
- 订单可以基于用户标识、业务场景和时间窗口生成业务请求号
- 支付可以直接用业务订单号做幂等键
- 消息可以用上游业务事件号或专门的消息键
实现上可以遵循这样一条主线:
- 请求第一次到达时,基于幂等键在存储中插入一条记录
- 插入成功说明是首个请求,正常执行业务并记录结果
- 后续同一幂等键再次到达时,直接返回已有结果,不再重复执行逻辑
这样同一笔业务就算重试多次,底层只会处理一次
重试变成补拿结果,不再是多次执行。
2、消息消费端加一层去重表
对消息队列类场景,可以采用生产端尽力投递、消费端保证幂等的模式:
- 为每条业务消息分配唯一业务号
- 消费端收到后先查去重表
- 若没有记录,执行业务处理,成功后写入去重表
- 若已有记录,直接视为已处理成功返回
这样就算消息被重复投递,队列重试多次,甚至消费方自己重试,最终业务只会落地一次。
================================
四、重试策略:按错误类型拆开,而不是一键多次
1、网络错误可以重试,但要有限度
针对超时、连接失败、短暂不可达这类网络问题,可以允许重试,但建议:
- 单次调用内限制重试次数,例如最多两次
- 在重试之间加入递增延迟,避免瞬间放大压力
- 总耗时超过上限时向上返回错误,交给人工或后续批处理
重试的目标是对抗短暂波动,而不是强行挤过一条已经拥塞的路。
2、业务错误不要重试,要改请求或改数据
诸如参数不合法、库存不足、状态不匹配、业务已关闭这一类错误,重试毫无意义,只会制造垃圾数据:
- 接口层只需立即返回失败
- 引导用户调整输入或等待状态变化
- 不要让重试机制去自动再试一轮
把暂时性故障和最终性错误分开,重试只发生在前者。
3、监控要加上重复率和平均调用次数
判断重试策略是否健康,不能只看成功率,还要看:
- 单笔业务平均被调用的次数
- 幂等键命中重复记录的比例
- 真实发生的重复扣款、重复通知、重复写入事件数量
一旦重复率明显上升,即便错误率下降,也说明重试策略在制造副作用,而不是改善可靠性。
================================
五、落地顺序:重试逻辑和出口稳定性一起改
1、三步改造你现在的重试
可以按这样的顺序动手:
- 第一步,列出所有涉及钱和重要通知的链路
给这些链路优先加业务幂等键和去重逻辑 - 第二步,按错误类型拆分重试策略
网络波动和服务暂时不可用可以有限重试
明确的业务失败直接返回,不进入重试机制 - 第三步,建立业务级观测
不仅看接口成功率,还要看平均调用次数和幂等重复命中率
先把最关键的几条链路改到这个标准,再一点点推广到其他模块。
2、用更稳定的出口,让重试只对抗真正的抖动
有时候重试乱飞,是因为底层线路本身太不稳定:
- 某些出口延迟抖得厉害,经常引发超时
- 某批节点间歇性丢包,重试都撞在有问题的那几条线上
- 服务端早已处理完,客户端却因为超时一次次重复请求
这时候,只改重试逻辑效果有限,还需要把底层出口做得足够可控,让重试只对付偶发问题。
易路代理在这一块能帮你承担一部分工程活:
- 线路类型丰富,可以把对幂等极其敏感的链路放在更稳的住宅线路上,把大量批量请求和数据采集放在机房线上,物理上先隔离风险
- 支持按业务建立线路组和标签,例如订单写入用一组出口,通知推送用一组出口,采集和报表用一组出口,应用只认标签不认具体地址,改线和扩容都在面板完成
- 面板里有各线路组的延迟和成功率统计,可以看出到底是哪一组线路逼出了最多重试,方便你同时调参重试策略和出口分配
比较务实的做法是:
- 先把最怕重复的一两条关键链路接到易路上稳定的出口组
- 配合幂等和重试改造一起上线
- 再通过易路面板的统计观察超时比例和重试次数变化,逐步调整参数
这样一来,重试不再是靠感觉乱撞,而是和幂等设计、出口质量一起组成一套有边界的可靠性机制,而不是隐形炸弹。