为什么很多重试逻辑,只在测试环境能跑通

博客剖析了重试逻辑在测试环境有效而线上失效的根本原因:HTTP请求体流被复用导致重试发送空报文。进一步指出常见误区,包括错误类型判断不全(忽略业务可重试码)、缺乏幂等控制、退避策略不合理(无上限指数退避)、链路追踪缺失等。强调重试需结合报文重建、可重试错误白名单、幂等键强制、总耗时限制等工程规范。

之前排查过一个很隐蔽的线上问题,测试环境反复验证重试都没问题,一上生产,重试几乎全部失效,当时卡了挺久。

最开始怀疑是网络波动、超时时间配短了,拉日志看,请求确实发出去了,重试代码分支也走到了,但是下游服务返回的结果一直没变,重试等于白跑。

后面慢慢捋清楚,问题根源不在重试本身,而在请求的载体。我们当时调用的是一个第三方的 HTTP 接口,请求体是提前序列化好放在变量里复用的。第一次请求之后,底层 http 客户端自动读取了 body 流,流被读完就关闭了。等到重试的时候,直接拿同一个 body 对象去发第二次请求,流是空的,下游拿到空报文,返回和第一次一样的报错,从日志上看就像重试没有生效。

这个坑在测试环境不容易暴露,本地或者测试服务器的 HTTP 客户端实现有时候会自动缓存 body 内容,流读完之后还能重复读取,所以自测、自动化用例全部通过。到了生产换了客户端版本,或者开启了连接复用,这个差异就直接暴露出来。

顺着这个问题,也梳理了平时写重试容易忽略的几个点。

很多人写重试,第一件事是判断错误类型,只对网络异常、超时这类错误重试,业务报错直接放弃。这个思路本身没问题,但很容易漏掉边界:有些接口返回的是 200 状态码,但是业务 code 标记服务繁忙,这种属于业务层面可重试,单纯捕获 io error 是抓不到的。还有一类是幂等风险,查询接口还好,写接口不加幂等标识就重试,很容易重复创建订单、重复扣减,很多事故都是这么来的。

还有一个常见误区,重试次数和退避时间拍脑袋配置。连续三次无间隔重试,在下游短暂抖动的时候还好,如果下游是过载限流,连续重试等于瞬间放大流量,把下游直接打垮,本来只是短暂故障,重试之后直接演变成雪崩。指数退避不是随便加就行,还要加上最大等待上限,不然大量请求堆积,请求链路超时,全部堆积在服务内存里。

另外还有链路追踪的问题。很多项目的 trace id 只在第一次请求的时候生成,重试请求没有更新 span,排查的时候只能看到第一次失败记录,后面几次重试的日志散落在别的地方,排查的时候很难把多次请求串起来。线上出问题,找不到完整链路,定位速度会慢很多。

后面我们调整了一套简单的规范:需要重试的请求,每次发起前都重新构造报文,而不是复用流对象;区分开 “可重试错误” 和 “不可重试错误”,业务异常单独配置白名单;写类接口强制要求带上幂等键,没有幂等的写操作禁止自动重试;同时限制重试的最大总耗时,不能超过上游给这个接口设置的整体超时。

到现在我也觉得,重试不是一个简单的 “失败再试一次” 的工具。它是一把双刃剑,写得好能提升接口容错能力,掩盖短暂的网络、下游抖动;考虑不周,要么线上完全不生效,要么直接放大故障。测试环境能跑通,永远不能当成线上可用的依据,很多差异只有在真实流量、真实客户端环境下才会显现。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值