当我们连接到一个失败的后端时, 通常希望不要立即重试(以避免泛滥的网络或服务器的请求), 而是做某种形式的指数 backoff.
我们有几个参数:
- INITIAL_BACKOFF (第一次失败重试前后需等待多久)
- MULTIPLIER (在失败的重试后乘以的倍数)
- JITTER (随机抖动因子).
- MAX_BACKOFF (backoff 上限)
- MIN_CONNECT_TIMEOUT (最短重试间隔)
建议 backoff 算法
以指数形式返回连接尝试的起始时间, 达到 MAX_BACKOFF 的极限, 并带有抖动.
- 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, 代码不多, 直接在代码上分析.
- 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 接口.
- func withBackoff(bs backoffStrategy) DialOption {
- return func(o *dialOptions) {
- o.bs = bs
- }
- }
- grpc.Dial(addr, grpc.withBackoff(mybackoff))
来源: http://www.bubuko.com/infodetail-3358091.html