当我们连接到一个失败的后端时,通常希望不要立即重试(以避免泛滥的网络或服务器的请求),而是做某种形式的指数backoff。
我们有几个参数:
- INITIAL_BACKOFF (第一次失败重试前后需等待多久)
- MULTIPLIER (在失败的重试后乘以的倍数)
- JITTER (随机抖动因子).
- MAX_BACKOFF (backoff上限)
- MIN_CONNECT_TIMEOUT (最短重试间隔)
建议backoff算法
以指数形式返回连接尝试的起始时间,达到MAX_BACKOFF的极限,并带有抖动。
1 2 3 4 5 6 7
| ConnectWithBackoff() current_backoff = INITIAL_BACKOFF current_deadline = now() + INITIAL_BACKOFF while (TryConnect(Max(current_deadline, now() + MIN_CONNECT_TIMEOUT))!= SUCCESS) SleepUntil(current_deadline) current_backoff = Min(current_backoff * MULTIPLIER, MAX_BACKOFF) current_deadline = now() + current_backoff + UniformRandom(-JITTER * current_backoff, JITTER * current_backoff)
|
参数默认值MIN_CONNECT_TIMEOUT
=20sec INITIAL_BACKOFF
=1sec MULTIPLIER
=1.6 MAX_BACKOFF
=120sec JITTER
=0.2
根据的确切的关注点实现(例如最小化手机的唤醒次数)可能希望使用不同的算法,特别是不同的抖动逻辑。
备用的实现必须确保连接退避在同一时间开始分散,并且不得比上述算法更频繁地尝试连接。
重置backoff
backoff应在某个时间点重置为INITIAL_BACKOFF
,以便重新连接行为是一致的,不管连接的是新开始的还是先前断开的连接。
当接收到SETTINGS
帧时重置backoff,在那个时候,我们确定这个连接被服务器已经接受了。
grpc-go
源码位于google.golang.org/grpc/backoff
,代码不多,直接在代码上分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import ( "math/rand" "time" ) // 对应上面的默认值但是没有实现MIN_CONNECT_TIMEOUT参数 var DefaultBackoffConfig = BackoffConfig{ MaxDelay: 120 * time.Second, baseDelay: 1.0 * time.Second, factor: 1.6, jitter: 0.2, } // backoffStrategy是backoff算法的接口 type backoffStrategy interface { // 通过重试次数返回在下一次重试之前等待的时间量 backoff(retries int) time.Duration } type BackoffConfig struct { MaxDelay time.Duration baseDelay time.Duration factor float64 jitter float64 } func setDefaults(bc *BackoffConfig) { md := bc.MaxDelay *bc = DefaultBackoffConfig if md > 0 { bc.MaxDelay = md } } // backoff算法的基础实现 func (bc BackoffConfig) backoff(retries int) time.Duration { if retries == 0 { return bc.baseDelay } backoff, max := float64(bc.baseDelay), float64(bc.MaxDelay) for backoff < max && retries > 0 { backoff *= bc.factor retries-- } if backoff > max { backoff = max } // Randomize backoff delays so that if a cluster of requests start at // the same time, they won't operate in lockstep. backoff *= 1 + bc.jitter*(rand.Float64()*2-1) if backoff < 0 { return 0 } return time.Duration(backoff) }
|
如果默认的backoff算法不满足需求的时候,还可以自定义backoff算法,通过实现backoffStrategy接口。
1 2 3 4 5 6 7
| func withBackoff(bs backoffStrategy) DialOption { return func(o *dialOptions) { o.bs = bs } } grpc.Dial(addr, grpc.withBackoff(mybackoff)) |