重试
Posted by 付辉 on Tuesday, July 24, 2018 共1021字为了克服网络问题,重试是我们常用的手段之一。但必须记住:重试的姿势非常重要。照应一句古话:“差若毫厘,谬以千里
”。
正确的姿势是便是:
如果请求没有成功,以指数型延迟重试
指数退避
Exponential backoff is an algorithm that uses feedback to multiplicatively decrease the rate of some process, in order to gradually find an acceptable rate
通俗的的讲,网络上的节点在发送数据冲突之后,不应立即尝试重发,而应该等待一段时间再发送,等待时间是指数增长,从而避免频繁的触发冲突。在计算机网络中,二进制指数退避算法常常作为避免网络堵塞的一部分,用于同一数据块的重发策略。
发生n次冲突之后,等待时间在0~2^n-1个间隙时间(slot time
)之间随机选择。比如第一次冲突之后,每个发送方会等待0或者1个间隙;第二次冲突之后,或等待时间会在0到3个间隙任意选择,依次类推,随着冲突次数的增加,发送方等待的时间可能成倍增加。
冲突达到一定次数,指数运算会停止,表示等待时间不会无限制增加下去。比如设置上限n=10,则最长等待时间为1023个时间间隙。同样,发送不可能永远的尝试下去,所以流程一般会在16次重试之后终止。
具体的退避算法:
1. 确定基本退避时间:争用期
2. 确定等待时间上限(max)。假设重传次数超过10次之后,k就不再增大。计算公式:k表示计算冲突等待时间的指数, k=min(重传次数, max)
3. 当重传达到16次仍不成功,则数据需要丢弃,并向高层报告。
退避算法的应用场景:
1. 三方支付中交易结果的推送通知。
2. 轮询,不间断的固定时间间隔的请求接口。
重试的问题
以下面的代码说明一下:
retryTimes := 1
for err != nil && retryTimes <= 3 {
//请求失败后,重新尝试
body, err = curl.GetCurlClient(curl.DefaultTimeout).PostJson(link, data)
retryTimes ++
}
假设后端服务每个接口的上限是10000QPS
,在超过这个上限之后,优雅降级系统会拒绝所有的额外请求(503的状态码就是干这个用的)。前端业务以固定的QPS
请求服务,如果网络稍微抖动,使得100QPS
请求失败。重试就会导致后端后续会接收到10100的QPS
。其中的100QPS
请求会因为服务过载而失败。
如果它们的网络还有问题,那么后续就会有200QPS
因为过载而失败。以此类推…
需要考虑的问题
放大服务负载的重试策略可能很难遇见。而且即使遇见了,也很难确定是不是重试导致的。但还是需要将重试的坏降到最小。
- 一定要使用随机化的,指数型递增的重试周期
- 将重试错误和非重试错误码做明确区分。这样业务端也可以依据错误码做请求策略调整
Example Code
指数退避的Go
代码:
//exponential back-off
func TryAgain(url string) error {
const timeout = 1 * time.Minute
deadline := time.Now().Add(timeout)
for tries := 0; time.Now().Before(deadline); tries++ {
//....
time.sleep(time.Second << tries)
}
}
参考文章: