连载中 9/20

超时与重试:微服务调用的组合拳

2026-08-26 · 561 阅读 · 0 评论 · 0 赞

每层都设了 3 秒,然后呢

一次调用穿八层服务,每层超时都设 3 秒——听起来很整齐。最坏情况:第一层等 3 秒发现下游没回来,重试一次又是 3 秒,链路末端的理论等待能堆到几十秒;而入口处的用户,三秒前就已经关掉了页面。上游等不起的时候,下游的一切努力都是浪费——超时不是每层独立的事,是全链路的预算问题。

超时预算:层层衰减

把总超时当成一笔预算往下分:入口给 1 秒,网关留 0.9 秒,核心服务留 0.7 秒,最底层的 DB 查询留 0.2 秒——原则只有一条:下游的超时必须小于上游的剩余时间,否则下游做得再精确,上游早就放弃等待了。更精细的做法是超时传递:每一跳把已消耗的时间传下去,让下游拿到的不是静态值,而是本次请求的剩余预算。

层级预算说明
用户端1s入口总预算
网关0.9s预留 0.1s 给出口
核心服务0.7s重试必须装进预算
下游 RPC0.3s连接与读超时分开设
DB0.2s慢查询直接掐断

重试的三个前提

重试是把双刃剑,开之前确认三件事:幂等——只有读操作和幂等的写操作可以直接重试,非幂等写先做幂等化改造;预算——重试必须装进超时预算,读超时 3 秒配重试 3 次等于没预算;退避——立即重试等于撞枪口,指数退避加随机抖动把重试流量摊开。预算用尽就止损,不要让重试风暴沿着调用链向上滚。

// 指数退避 + 随机抖动
long backoff(int attempt) {
    long base = 100L;                       // 基数 100ms
    long exp  = base << attempt;            // 100, 200, 400 ...
    long jitter = (long) (exp * 0.2 * Math.random()); // 20% 抖动
    return exp - jitter / 2 + random(jitter);
}
// 重试次数 1~2 次封顶,且必须在剩余预算内执行

和熔断的先后关系

重试和熔断是搭配使用的:重试解决偶发抖动,熔断解决持续性故障。次序很关键:重试发生在熔断器之内——失败率统计要包含重试失败的请求,下游真挂了熔断器才能基于真实失败率尽快打开;熔断打开期间重试直接短路,半开状态只放少量探测请求。反过来先重试后熔断,故障期流量先被放大一圈才被熔断,下游死得更快。熔断器的三态模型这里不展开,先记结论:先熔断保护,重试才有意义。

一张默认值清单

给一套能直接抄的基线:连接超时 500ms~1s,建连不该超过 1 秒;读超时按接口 P99 的 2~3 倍定,不要拍脑袋统一 3 秒;重试 1~2 次封顶;退避基数 100ms、抖动 20%;所有参数进配置中心,支持运行时调整——超时是调出来的,不是定出来的。下一篇聊流量进来的第一站:API 网关。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#超时治理#重试#指数退避#超时传递#熔断

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞