构建

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

作者: 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;
}
RETRY IMMEDIATELY — 64 attempts. 18 attempts inside one 200ms window. try 1〜16,间隔 20ms
RETRY IMMEDIATELY — 64 attempts. 18 attempts inside one 200ms window. try 1〜16,间隔 20ms
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);
}
EXPONENTIAL + JITTER — 25 attempts. 13 attempts inside one 200ms window. try 1〜6
EXPONENTIAL + JITTER — 25 attempts. 13 attempts inside one 200ms window. try 1〜6
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