在收银台不在的时候结尾我们写过这样一段。
没有自动重试。 是测试装置显式重连的。真实应用应该在后台周期性尝试,而那个周期和退避在这里没有定。
这一篇把那个周期定下来。而且顺便量一量,周期定错了会发生什么。
从接收方来数
关于重试的文章绝大多数是调用方的故事 — 我怎么才能把请求送进去。
这个样例的服务器站在另一边。它数的是来了多少次尝试,以及其中有多少次挤在同一个 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: 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: 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 倍。