构建

重试的代价由谁来付 — 不是打电话的那一方,是接电话的那一方

关于重试的文章大多是从调用方写的。这一篇从接收方来数。四个调用者用两种方式熬过了 700 毫秒的故障,两次最后都通过了。不同的是这期间网关被砸了多少次 — 64 次和 25 次。

作者: makemind · 2026年7月16日

收银台不在的时候结尾我们写过这样一段。

没有自动重试。 是测试装置显式重连的。真实应用应该在后台周期性尝试,而那个周期和退避在这里没有定。

这一篇把那个周期定下来。而且顺便量一量,周期定错了会发生什么。

从接收方来数

关于重试的文章绝大多数是调用方的故事 — 我怎么才能把请求送进去。

这个样例的服务器站在另一边。它数的是来了多少次尝试,以及其中有多少次挤在同一个 200 毫秒里。

/// That second number is the one that matters. A gateway that is briefly down
/// does not care that you tried again. It cares how many of you tried again at
/// the same instant, because that is what keeps it down.

一个短暂宕机的网关,对「你又打了一次」这个事实不感兴趣。它在意的是有多少人在同一瞬间又打了一次。 因为那才是让它一直躺着的东西。

赶时间时写出来的东西

/// Try again immediately. This is what gets written when the retry is added in
/// a hurry, and it is the strategy that turns a short outage into a long one.
class NoBackoff extends Backoff {
  @override
  int waitBefore(int n) => n == 1 ? 0 : 20;
}
立刻重试 —— 共 64 次,其中 18 次挤在同一个 200ms 窗口里。列表逐行显示每次尝试及其之前的等待
立刻重试 —— 共 64 次,其中 18 次挤在同一个 200ms 窗口里。列表逐行显示每次尝试及其之前的等待
RETRY IMMEDIATELY: 4 callers, 64 attempts total, 4 accepted, peak 18
  caller 1 attempt 1: straight away -> DOWN at 12ms
  caller 1 attempt 2: after 20ms -> DOWN at 58ms
  ...
  caller 1 attempt 16: after 20ms -> ACCEPTED at 708ms

通过了。 熬过了 700 毫秒的故障,四个人都完成了支付。

而网关在这期间被砸了 64 次。

每次翻倍,然后再抖一抖

@override
int waitBefore(int n) {
  if (n == 1) return 0;
  final base = 60 * (1 << (n - 2)); // 60, 120, 240, 480 ...
  // Full jitter: anywhere in [0, base]. Spreading matters more than being
  // punctual — nobody is waiting on an exact millisecond here.
  return _rng.nextInt(base + 1);
}
指数退避 + 抖动 —— 同一次故障共 25 次尝试,最坏窗口 13 次。下面一行把两种策略的代价并排写出
指数退避 + 抖动 —— 同一次故障共 25 次尝试,最坏窗口 13 次。下面一行把两种策略的代价并排写出
EXPONENTIAL + JITTER: 4 callers, 25 attempts total, 4 accepted, peak 13
  caller 1 attempt 1: straight away -> DOWN at 13ms
  caller 1 attempt 2: after 53ms  -> DOWN at 70ms
  caller 1 attempt 3: after 87ms  -> DOWN at 159ms
  caller 1 attempt 4: after 143ms -> DOWN at 305ms
  caller 1 attempt 5: after 389ms -> DOWN at 697ms
  caller 1 attempt 6: after 449ms -> ACCEPTED at 1150ms

同样的故障、同样的人数、同样的结果。 四个人都完成了支付。

尝试从 64 次降到 25 次。 少了 2.6 倍。

此内容需要开发者或更高等级

登录并升级您的方案即可继续阅读。

查看方案
Twitter