当代码在等待某个延迟时间不确定的条件时,看起来很多人选择使用指数退避,即等待N秒,检查条件是否满足;如果不是,请等待 2N 秒,检查条件等。与检查恒定/线性增加的时间跨度相比,这有什么好处?
3 回答
在同时尝试做某事会相互干扰以致没有成功的情况下,指数退避很有用。在这种情况下,让设备在太小的窗口中随机尝试操作将导致大多数尝试失败并不得不重试。只有当窗口变得足够大时,尝试才有很大的成功可能性。
如果事先知道有 16 台设备想要进行通信,则可以选择最适合该负载水平的窗口大小。但实际上,竞争设备的数量通常是未知的。每次重试时窗口大小加倍的指数回退的优势在于,无论竞争实体的数量如何:
大多数操作成功的窗口大小通常在大多数操作成功的最小窗口大小的两倍之内,
大多数在该窗口大小下失败的操作将在下一次尝试中成功(因为大多数早期操作都会成功,这将导致不到一半的操作竞争两倍大的窗口),并且
- 所有尝试所需的总时间最终只会是最后一次所需时间的两倍左右。
如果窗口不是每次加倍,而是简单地增加一个常数,那么重试操作直到窗口达到可用大小所花费的时间将与所需窗口大小的平方成正比。虽然最终的窗口大小可能比使用指数回退时要小,但所有尝试的总成本会大得多。
这是 TCP 拥塞控制的行为。如果网络极度拥塞,则实际上没有流量通过。如果每个节点在检查之前都等待一段时间,那么仅用于检查的流量将继续阻塞网络,并且拥塞永远不会解决。类似地,对于检查之间的线性增加时间,在解决拥塞之前可能需要很长时间。
假设您指的是在执行操作之前测试条件:
- 当测试条件的成本与执行操作的成本(例如网络拥塞)相当时,指数退避是有益的。
- 如果测试条件的成本要小得多(或可以忽略不计),那么线性或恒定等待可以更好地工作,前提是条件更改所需的时间也可以忽略不计。
例如,如果您的条件是对数据库的复杂(缓慢)查询,并且操作是对同一数据库的更新,那么每次检查条件都会对数据库性能产生负面影响,并且在某些时候,没有指数退避,由多个参与者检查条件可能足以使用所有数据库资源。
但是,如果条件只是轻量级内存检查(fi 是关键部分),并且操作仍然是数据库的更新(最多比检查慢千分之一),并且如果条件翻转可以忽略不计在动作一开始的时间(通过进入临界区),那么常数或线性退避就可以了。实际上在这种特殊情况下,指数退避将是有害的,因为它会在低负载情况下引入延迟,并且更有可能在高负载情况下导致超时(即使处理带宽足够)。
总而言之,指数退避是一把锤子:它非常适合钉子,而不是螺钉 :)