“失败了就指数退避”是重试的标准答案,但如果所有客户端同时开始、同时失败,没有抖动的指数退避只是让它们每一轮同时回来一次。这篇用 200 个客户端和一个先故障后限流的本机服务器,把五种策略在服务器端打出的请求节奏量出来。
策略 服务器收到请求 任意 0.1 秒内最多 任意 1 秒内最多 全部成功于 放弃的客户端 最多尝试 立刻重试 2400 个 994 个 2400 个 — 200 0 固定延迟 0.5 秒 1260 个 200 个 400 个 4.8 秒 0 10 指数退避(无抖动) 1766 个 200 个 800 个 31.6 秒 26 12 指数退避 + 完全抖动 1167 个 411 个 980 个 5.3 秒 0 8 去相关抖动 918 个 200 个 727 个 2.9 秒 0 8
立刻重试:12 次机会在 1 秒的故障期内就用光,服务器总共收到 2400 个请求,任意 0.1 秒内最多 994 个,200 个客户端全部放弃。重试而没有间隔,等于对已经出问题的服务器加压。
固定间隔与无抖动的指数退避:固定 0.5 秒时 200 个客户端每 0.5 秒同时回来一次;服务器每轮只能处理 20 个。无抖动的指数退避看起来更“聪明”,但所有客户端同步,每一轮仍是 200 个同时到达,间隔越来越长而一轮只成功 20 个,最后 26 个客户端用完 12 次也没成功,总共 31.6 秒。
加抖动:完全抖动(在 0 到退避上限之间随机)5.3 秒全部成功;去相关抖动(下一次等待取决于上一次)2.9 秒、只用 918 个请求。开头的 0.1 秒内仍有 200–400 个请求,因为所有客户端是同时开始的——抖动解决的是后续重试的同步,不能消除一开始的集中。
指数退避(无抖动):服务器收到请求 [1570, 1438, 1840, 1840];任意 0.1 秒内最多 [200, 200, 200, 200];全部成功于 [31.6, 26.5, 31.6, 31.6] 秒 指数退避 + 完全抖动:服务器收到请求 [1165, 1194, 1168, 1184];任意 0.1 秒内最多 [339, 390, 387, 388];全部成功于 [3.8, 4.6, 3.5, 4.1] 秒
无抖动的指数退避不依赖随机数,4 个种子下任意 0.1 秒内都是正好 200 个(客户端同步),完成时间 26.5–31.6 秒;完全抖动 3.8–4.6 秒。服务器“同时处理超过 20 个就拒绝”依赖线程调度,所以每次运行细节不同:我把整个脚本跑了两遍,固定间隔 5.8 秒与 4.8 秒,完全抖动 5.6 秒与 5.3 秒,去相关抖动 3.6 秒与 2.9 秒,无抖动指数退避放弃的客户端 20 个与 26 个;文中取第二遍。不变的结论是:无抖动同步重试最慢,抖动最快。