找回密码
立即注册
搜索
发回帖 发新帖

6275

积分

0

好友

791

主题
发表于 2 小时前 | 查看: 1| 回复: 0

先说结论:超时重试是个甜蜜的陷阱。网络抖动时它能救你一命,但稍有不慎,它也能把下游服务活活打死。在云栈社区的技术讨论中,类似的线上事故可以说是屡见不鲜。

1个请求变6个:超时重试的雪崩放大效应

现场还原

凌晨 3 点 07 分,告警突然叫醒了值班同学。

订单服务的 QPS 从平时的 200 猛然拉满到 1800——但业务量没有任何变化。与此同时,上游的服务 A 开始疯狂报错,日志里清一色的 Read timed out。

值班同学第一反应是“订单服务是不是数据库慢查询了”,登上机器一看:数据库连接池被打满,全部在处理重复的创建订单请求。

再往上游追查,真相浮出水面:

服务 A 调用订单服务创建订单时,接口超时时间(ReadTimeout)设置的是 1 秒。而订单服务在高峰期创建订单的平均耗时是 1.2 秒。

于是,一连串的连锁反应开始了:

  1. A 发请求 → 1 秒后超时 → Feign 认为失败 → 重试
  2. 重试又发出了 1 个新请求 → 又超时 → 再重试
  3. 默认配置下重试 5 次,加上第一次,一共 6 个请求打到了订单服务
  4. 6 个请求都在创建订单,数据库写了 6 条记录
  5. 用户愤怒投诉:“我就点了一下,怎么扣了 6 次钱?”

一句话总结:ReadTimeout 设小了 + 默认重试开着 = 上游超时,下游被打穿。

为什么十几个请求能把服务打挂

这里有个很多人忽略的技术细节:重试不是“再来一次”,而是再来一次乘以上游的并发量。

假设 A 服务有 20 个线程在调订单服务,每个线程超时重试 5 次,那订单服务实际承受的 QPS 就是:

原始 QPS × (1 + 重试次数) = 200 × 6 = 1200

更糟的是雪崩效应:订单服务被打慢 → 更多请求超时 → 更多重试 → 订单服务更慢 → 直到彻底崩溃。

这就是经典的重试风暴(Retry Storm)。严格来说,它不是 bug,而是默认配置运转下的必然结果。

分层理解:Feign 里到底有几个超时

很多人只知道“Feign 有个超时”,其实这里有两层独立超时,任何一层配置错误都可能引发事故。

超时类型 含义 设错了会怎样
connectTimeout TCP 建连的时间上限 设太长:线程被卡住不释放,线程池被打满
readTimeout 建连成功后,等待响应的上限 设太短:正常慢请求被当成失败,触发重试

关键认知:这两个超时是独立生效的。很多事故的本质其实是“连接超时设得合理,但读超时设得比下游正常耗时还短”。这就等于你自己亲手制造了一个必然失败的条件。

配置长这样:

feign:
  client:
    config:
      order-service:
        connectTimeout: 2000  # 2 秒建连
        readTimeout: 5000    # 5 秒等响应,必须 > 下游 P99 耗时
  retryer:
    Retryer.Default:
      period: 100  # 重试间隔
      maxPeriod: 1000
      maxAttempts: 1  # 非幂等接口必须设为 1,即不重试

最后那行 maxAttempts: 1 是重点,下面细说。

生活化比喻:催菜催出三碗面

你在餐厅点了一碗牛肉面。

  • 第 1 分钟没上 → 你催服务员:“好了吗?”
  • 第 3 分钟还没上 → 你又催:“到底好了没?”
  • 第 5 分钟 → 你第三次催

于是厨房收到了三条“来一碗牛肉面”的指令,端出来三碗。结账时你被收了三份钱。

催菜催出三碗面:超时重试的生活化比喻

复盘这个场景,其实存在两个致命问题:

  1. 你的等待耐心(readTimeout)设得太短了——后厨正常出餐要 8 分钟,你 3 分钟就催一次,注定每次都在“催已经在下锅的面”。
  2. “下面条”这个动作不幂等——每执行一次就真的多做一碗。如果当初点单时给的是“取餐号 #827”,厨房看到重复请求知道是同一个订单,就不会做三碗。

现实中的服务调用完全一样。重试发生在网络层,下游根本不知道“这是重试还是新请求”——除非你主动告诉它。

核心机制:幂等性是唯一的解药

幂等检查站:去重表拦截重复请求

幂等性(Idempotency)的定义很朴素:同一个操作执行一次和执行多次,效果是相同的。

  • 查订单(GET):天然幂等,随便重试
  • 创建订单(POST):默认不幂等,必须自己实现

实现手段主要有三种,这里按可靠性从高到低排序:

方案一:唯一请求 ID + 去重表(最稳)

调用方生成一个全局唯一 ID(比如 UUID),跟着请求一起发。下游每次先查这个 ID 是否处理过:

// 调用方:生成唯一 ID 放进 header
String requestId = UUID.randomUUID().toString();
feignClient.createOrder(requestId, orderDTO);

// 下游:先查去重表,处理过就直接返回上次结果
if (dedupTable.exists(requestId)) {
    return dedupTable.getResult(requestId); // 直接返回,不重复执行业务
}
Order order = doCreateOrder(orderDTO);
dedupTable.save(requestId, order); // 记下来,供下次重试命中
return order;

方案二:数据库唯一约束(最简单)

订单号做唯一索引。重试时插入记录会撞上唯一键,捕获异常后查询已有记录返回即可。代价是会给数据库带来不可避免的无效写入压力。

方案三:Token 机制(防重复提交,但不防重试)

前端先取一个 token,提交时带上,服务端消费掉。这个只能防用户连点,防不了 Feign 重试,因为重试是服务端发起的。

重试到底该怎么配

三条原则,按优先级严格执行:

  1. 非幂等接口,直接关闭重试。创建订单、支付、发货这类接口,maxAttempts 设成 1。宁可让上游收到失败,也不能让下游重复执行。
  2. 只给幂等接口开重试,且必须带退避(Backoff)。固定间隔重试等于有节奏地击打下单服务;指数退避(100ms → 200ms → 400ms)能给下游留出喘息空间。
  3. 重试次数坚决不要超过 3 次。重试是兜底手段,不是常态操作。如果 3 次都失败,说明下游已经出了大问题,应该立即走熔断降级而不是继续无脑重试。

顺带说一句,Retryer.Default 的默认值是 maxAttempts=5(加上首次共 6 次)。什么都不配,就等于默默接受了 6 倍的流量放大。

面试速记卡

Q:Feign 的 connectTimeout 和 readTimeout 有什么区别?

A:connectTimeout 是 TCP 三次握手建连的时间上限,超了说明网络不通或对端没监听;readTimeout 是建连成功后等待响应的时间上限,超了说明对端正在处理但速度太慢。两个独立生效,配置时必须让 readTimeout 大于下游接口的 P99 耗时。

Q:为什么重试会导致重复下单?

A:重试发生在网络层,下游无法区分“这是新的请求”还是“上次那个请求的重复投递”。如果接口不是幂等的,重试就会造成业务重复执行。解法是调用方传唯一请求 ID,下游用去重表拦截。

Q:Feign 默认重试几次?怎么改?

A:Retryer.Default 默认 maxAttempts=5,即首次 + 5 次重试共 6 次请求。通过注册 Retryer.NEVER_RETRY 关闭,或自定义 Retryer Bean 控制次数和退避间隔。

Q:怎么判断一个接口能不能开重试?

A:看幂等性。GET、PUT、DELETE 天然幂等(幂等的 PUT 指同一份数据覆盖写入),POST 通常不幂等。不确定的一律不开重试。

Q:重试和熔断是什么关系?

A:重试是“再试一次,也许能成功”;熔断是“连续失败太多次,先别试了”。两者配合使用:重试 2-3 次仍失败就立即触发熔断,直接返回降级响应,杜绝流量继续放大。

这篇在知识体系里的位置

属于「分布式」分类。之前讲过分布式锁(防并发)和 2PC/TCC(跨服务事务一致性),这篇讲的是可靠性工程里的第三条腿:幂等性。

三者解决的是不同维度的问题:

机制 防御目标 典型场景
分布式锁 多个请求同时改同一份数据 秒杀扣库存
2PC/TCC 跨服务操作需要一起成功或一起失败 跨行转账
幂等性 同一个请求被重复投递 超时重试、消息重投

新增知识点:Feign 双层超时模型、Retryer 默认行为、幂等性三种实现方案、重试风暴的雪崩原理。

数据来源

  • 51CTO:Java 开发:OpenFeign 调用经常报错?超时、重试、日志、异常处理优化方案

    https://www.51cto.com/article/857402.html

  • Feign Retryer 默认配置: maxAttempts=5, period=100ms, maxPeriod=1000ms
  • 案例细节基于常见线上事故形态重构,非特定公司真实事件



上一篇:信息安全管理与评估大赛真题解析:网络配置与渗透测试实战答案汇总
下一篇:AI 编程助手总过度设计?Ponytail 七级规则让 agent 少写 54% 代码
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-8 03:55 , Processed in 0.069732 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表