【golang】26、retry-go 使用示例和源码解析

news2025/1/21 15:27:08

文章目录

  • 一、使用方法
    • 1.1 http 示例
      • 1.1.1 retry.Do
      • 1.1.2 retry.DoWithData
      • 1.1.3 OnRetry
      • 1.1.4 根据 error 的类型,决定 delay 的时长
      • 1.1.5 自定义 retry function
  • 二、API
    • 2.1 Do 执行
      • 2.1.1 Do
      • 2.1.2 DoWithData
    • 2.2 Delay 策略
    • 2.3 错误处理
      • 2.3.1 Unwrap
      • 2.3.2 UnwrappedErrors
    • 2.4 type config
    • 2.5 Option,如 OnRetry
      • 2.5.1 OnRetry
      • 2.5.2 RetryIf
    • 2.6 Context
    • 2.7 Delay
  • 三、实现
    • 3.1 Config 和 Option
      • 3.1.1 Delay 实现
        • 3.1.1.1 BackOffDelay
        • 3.1.1.2 CombineDelay
      • 3.1.2 Retry 策略
        • 3.1.2.1 OnRetry
    • 3.2 主流程 DoWithData
      • 3.2.1 Do 封装函数

在分布式情况下,因为网络或服务不稳定,经常需要 retry。golang 下有 retry-go 库,封装了常见的重试配置,很方便扩展。项目地址是 https://github.com/avast/retry-go。

一、使用方法

1.1 http 示例

1.1.1 retry.Do

package main

import (
    "fmt"
    "github.com/avast/retry-go"
    "io"
    "net/http"
)

func main() {
    urls := []string{
       "http://example.com",   // url 真实存在
       "http://not-exist.com", // url 不存在
    }
    for _, url := range urls {
       f(url)
    }
}

func f(url string) {
    fmt.Println("开始处理: ", url)
    var body []byte
    err := retry.Do(func() error {
       resp, err := http.Get(url)
       if err != nil {
          return err
       }
       defer resp.Body.Close()
       body, err = io.ReadAll(resp.Body)
       if err != nil {
          return err
       }
       return nil
    })
    if err != nil {
       panic(err)
    }
    _ = body
    fmt.Println("处理成功")
}

// go run main.go
开始处理:  http://example.com
处理成功

开始处理:  http://not-exist.com
panic: All attempts fail:
#1: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#2: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#3: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#4: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#5: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#6: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#7: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#8: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#9: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#10: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host

1.1.2 retry.DoWithData

package main

import (
    "github.com/avast/retry-go/v4"
    "github.com/sirupsen/logrus"
    "io"
    "net/http"
)

func main() {
    urls := []string{
       "http://example.com",   // url 真实存在
       "http://not-exist.com", // url 不存在
    }
    for _, url := range urls {
       //fDo(url)
       fDoWithData(url)
    }
}

func fDo(url string) {
    logrus.Infoln("开始: ", url)
    err := retry.Do(func() error {
       resp, err := http.Get(url)
       if err != nil {
          return err
       }
       defer resp.Body.Close()
       body, err := io.ReadAll(resp.Body)
       if err != nil {
          return err
       }
       _ = body
       return nil
    })
    if err != nil {
       logrus.Errorln("失败: ", err)
       return
    }
    logrus.Infoln("成功")
}

func fDoWithData(url string) {
    logrus.Infoln("开始: ", url)
    body, err := retry.DoWithData(
       func() ([]byte, error) {
          resp, err := http.Get(url)
          if err != nil {
             return nil, err
          }
          defer resp.Body.Close()
          body, err := io.ReadAll(resp.Body)
          if err != nil {
             return nil, err
          }
          return body, nil
       },
    )
    if err != nil {
       logrus.Errorln("失败: ", err)
       return
    }
    _ = body
    logrus.Infoln("成功")
}

// go run main.go
INFO[0000] 开始:  http://example.com                      
INFO[0000] 成功                                           
INFO[0000] 开始:  http://not-exist.com                    
ERRO[0052] 失败:  All attempts fail:
#1: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#2: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#3: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#4: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#5: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#6: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#7: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#8: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#9: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host
#10: Get "http://not-exist.com": dial tcp: lookup not-exist.com: no such host 

1.1.3 OnRetry

设置当 retry 时的回调函数

package retry_test

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/avast/retry-go/v4"
    "github.com/stretchr/testify/assert"
)

// TestErrorHistory shows an example of how to get all the previous errors when
// retry.Do ends in success
func TestErrorHistory(t *testing.T) {
    attempts := 3 // server succeeds after 3 attempts
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if attempts > 0 {
            attempts--
            w.WriteHeader(http.StatusBadGateway)
            return
        }
        w.WriteHeader(http.StatusOK)
    }))
    defer ts.Close()
    var allErrors []error
    err := retry.Do(
        func() error {
            resp, err := http.Get(ts.URL)
            if err != nil {
                return err
            }
            defer resp.Body.Close()
            if resp.StatusCode != 200 {
                return fmt.Errorf("failed HTTP - %d", resp.StatusCode)
            }
            return nil
        },
        retry.OnRetry(func(n uint, err error) {
            allErrors = append(allErrors, err)
        }),
    )
    assert.NoError(t, err)
    assert.Len(t, allErrors, 3)
}

1.1.4 根据 error 的类型,决定 delay 的时长

// This test delay is based on kind of error
// e.g. HTTP response [Retry-After](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
package retry_test

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "testing"
    "time"

    "github.com/avast/retry-go/v4"
    "github.com/stretchr/testify/assert"
)

// 定义第一种 error
type RetryAfterError struct {
    response http.Response
}
func (err RetryAfterError) Error() string {
    return fmt.Sprintf(
        "Request to %s fail %s (%d)",
        err.response.Request.RequestURI,
        err.response.Status,
        err.response.StatusCode,
    )
}

// 定义第二种 error
type SomeOtherError struct {
    err        string
    retryAfter time.Duration
}
func (err SomeOtherError) Error() string {
    return err.err
}

func TestCustomRetryFunctionBasedOnKindOfError(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "hello")
    }))
    defer ts.Close()

    var body []byte

    err := retry.Do(
        func() error {
            resp, err := http.Get(ts.URL)

            if err == nil {
                defer func() {
                    if err := resp.Body.Close(); err != nil {
                        panic(err)
                    }
                }()
                body, err = ioutil.ReadAll(resp.Body)
            }

            return err
        },
        retry.DelayType(func(n uint, err error, config *retry.Config) time.Duration { // 设置 retry 的一个 Option
            switch e := err.(type) { // 判断调用函数的返回值 error
            case RetryAfterError: // 如果是此 error 的话,则从 Header 中解析出一个 time.Time, 并计算 now() 与 其的 time.Duration
                if t, err := parseRetryAfter(e.response.Header.Get("Retry-After")); err == nil {
                    return time.Until(t)
                }
            case SomeOtherError: // 因为此 error 已包含了一个 time.Duration,则直接使用
                return e.retryAfter
            }

            // 若未匹配 error,则使用默认的 backoffdelay 策略
            return retry.BackOffDelay(n, err, config)
        }),
    )

    assert.NoError(t, err)
    assert.NotEmpty(t, body)
}

// use https://github.com/aereal/go-httpretryafter instead
func parseRetryAfter(_ string) (time.Time, error) {
    return time.Now().Add(1 * time.Second), nil
}

1.1.5 自定义 retry function

package retry_test

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "strconv"
    "testing"
    "time"

    "github.com/avast/retry-go/v4"
    "github.com/stretchr/testify/assert"
)

// RetriableError is a custom error that contains a positive duration for the next retry
type RetriableError struct {
    Err        error
    RetryAfter time.Duration
}

// Error returns error message and a Retry-After duration
func (e *RetriableError) Error() string {
    return fmt.Sprintf("%s (retry after %v)", e.Err.Error(), e.RetryAfter)
}

var _ error = (*RetriableError)(nil)

// TestCustomRetryFunction shows how to use a custom retry function
func TestCustomRetryFunction(t *testing.T) {
    // 首先定义 server 的行为
    attempts := 5 // server succeeds after 5 attempts
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if attempts > 0 {
            // 前 5 次会返回 HTTP 429 通知 client 1 秒后重试,第 6 次返回 HTTP 200
            // inform the client to retry after one second using standard
            // HTTP 429 status code with Retry-After header in seconds
            w.Header().Add("Retry-After", "1")
            w.WriteHeader(http.StatusTooManyRequests)
            w.Write([]byte("Server limit reached"))
            attempts--
            return
        }
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("hello"))
    }))
    defer ts.Close()

    var body []byte

    // 其次定义 client 的行为,通过 retry.Do() 封装 http 调用
    err := retry.Do(
        // 执行的函数会返回 error
        func() error {
            resp, err := http.Get(ts.URL)

            if err == nil {
                defer func() {
                    if err := resp.Body.Close(); err != nil {
                        panic(err)
                    }
                }()
                body, err = ioutil.ReadAll(resp.Body)
                if resp.StatusCode != 200 {
                    err = fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
                    if resp.StatusCode == http.StatusTooManyRequests {
                        // check Retry-After header if it contains seconds to wait for the next retry
                        // 解析 Retry-After header,其中包含了下次重试的秒数
                        if retryAfter, e := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 32); e == nil {
                            // the server returns 0 to inform that the operation cannot be retried
                            if retryAfter <= 0 {
                                return retry.Unrecoverable(err)
                            }
                            return &RetriableError{
                                Err:        err,
                                RetryAfter: time.Duration(retryAfter) * time.Second, // 使用从 server 返回的 Retry-After header 中解析的秒数,作为下次重试的间隔
                            }
                        }
                        // A real implementation should also try to http.Parse the retryAfter response header
                        // to conform with HTTP specification. Herein we know here that we return only seconds.
                    }
                }
            }

            return err
        },

        // 执行完 func() error 后会得到 err,会执行 retry.DelayType(err)
        // 如果 err 是 *RetriableError 类型,会返回 e.RetryAfter 的 time.Duration
        // 否则如果 err 不是 *RetriableError 类型,会返回默认的 retry.BackOffDelay(n, err, config)
        retry.DelayType(func(n uint, err error, config *retry.Config) time.Duration {
            fmt.Println("Server fails with: " + err.Error())
            if retriable, ok := err.(*RetriableError); ok {
                fmt.Printf("Client follows server recommendation to retry after %v\n", retriable.RetryAfter)
                return retriable.RetryAfter
            }
            // apply a default exponential back off strategy
            return retry.BackOffDelay(n, err, config)
        }),
    )

    fmt.Println("Server responds with: " + string(body))

    assert.NoError(t, err)
    assert.Equal(t, "hello", string(body))
}

// go run
=== RUN   TestCustomRetryFunction
Server fails with: HTTP 429: Server limit reached (retry after 1s)
Client follows server recommendation to retry after 1s
Server fails with: HTTP 429: Server limit reached (retry after 1s)
Client follows server recommendation to retry after 1s
Server fails with: HTTP 429: Server limit reached (retry after 1s)
Client follows server recommendation to retry after 1s
Server fails with: HTTP 429: Server limit reached (retry after 1s)
Client follows server recommendation to retry after 1s
Server fails with: HTTP 429: Server limit reached (retry after 1s)
Client follows server recommendation to retry after 1s
Server responds with: hello
--- PASS: TestCustomRetryFunction (5.01s)
PASS
ok      github.com/avast/retry-go/v4/examples   5.473s

单测其实就是最好的 readme,可以看出使用方法的。

二、API

2.1 Do 执行

2.1.1 Do

// 没有返回体
func Do(retryableFunc RetryableFunc, opts ...Option) error

2.1.2 DoWithData

// 返回数据, retryableFunc 回调函数返回 (T, error), 整个外层函数返回的也是 (T, error)
func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error) {

// 使用示例
func TestDoWithDataFirstOk(t *testing.T) {
    returnVal := 1

    var retrySum uint
    // 这种使用形式是简写了类型 T = int,也可以写为 DoWithData[int]();
    val, err := DoWithData(
        func() (int, error) { return returnVal, nil }, // 函数的第一个返回值是 int
        OnRetry(func(n uint, err error) { retrySum += n }),
    )
    assert.NoError(t, err)
    assert.Equal(t, returnVal, val)
    assert.Equal(t, uint(0), retrySum, "no retry")
}

2.2 Delay 策略

// 定义回调函数类型:根据返回的 err, 和失败的 n 次,可以解析得到对应的 time.Duration
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration

// 如下实现:
// BackOffDelay is a DelayType which increases delay between consecutive retries
func BackOffDelay(n uint, _ error, config *Config) time.Duration

// 固定的延迟间隔
func FixedDelay(_ uint, _ error, config *Config) time.Duration

// 随机
func RandomDelay(_ uint, _ error, config *Config) time.Duration

// 聚合多种 DelayTypeFunc 为一种
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc

2.3 错误处理

// 定义了内部的 error struct

// 通过 error.Is() 判断类型是否匹配
func IsRecoverable(err error) bool

func Unrecoverable(err error) error

2.3.1 Unwrap

// 解封装 error
func (e Error) Unwrap() error

Unwrap the last error for compatibility with `errors.Unwrap()`. When you need to
unwrap all errors, you should use `WrappedErrors()` instead.

    err := Do(
        func() error {
            return errors.New("original error")
        },
        Attempts(1),
    )

    fmt.Println(errors.Unwrap(err)) # "original error" is printed

Added in version 4.2.0.

2.3.2 UnwrappedErrors

func (e Error) WrappedErrors() []error

WrappedErrors returns the list of errors that this Error is wrapping. It is an
implementation of the `errwrap.Wrapper` interface in package
[errwrap](https://github.com/hashicorp/errwrap) so that `retry.Error` can be
used with that library.

2.4 type config

type Config struct {
}

2.5 Option,如 OnRetry

type Option func(*Config)

// n 是重试次数
type OnRetryFunc func(n uint, err error)

// Attempts set count of retry. Setting to 0 will retry until the retried function succeeds. default is 10
func Attempts(attempts uint) Option

// 设置 仅某种 err 重试 attempts 次
func AttemptsForError(attempts uint, err error) Option

2.5.1 OnRetry

func OnRetry(onRetry OnRetryFunc) Option

OnRetry function callback are called each retry

log each retry example:

    retry.Do(
        func() error {
            return errors.New("some error")
        },
        retry.OnRetry(func(n uint, err error) {
            log.Printf("#%d: %s\n", n, err)
        }),
    )

2.5.2 RetryIf

func RetryIf(retryIf RetryIfFunc) Option

RetryIf controls whether a retry should be attempted after an error (assuming
there are any retry attempts remaining)

skip retry if special error example:

    retry.Do(
        func() error {
            return errors.New("special error")
        },
        retry.RetryIf(func(err error) bool {
            if err.Error() == "special error" {
                return false
            }
            return true
        }),
    )
    
    
    
    
    By default RetryIf stops execution if the error is wrapped using
`retry.Unrecoverable`, so above example may also be shortened to:

    retry.Do(
        func() error {
            return retry.Unrecoverable(errors.New("special error"))
        }
    )

2.6 Context

func Context(ctx context.Context) Option

Context allow to set context of retry default are Background context

example of immediately cancellation (maybe it isn't the best example, but it
describes behavior enough; I hope)

    ctx, cancel := context.WithCancel(context.Background())
    cancel()

    retry.Do(
        func() error {
            ...
        },
        retry.Context(ctx),
    )

2.7 Delay

// Delay set delay between retry default is 100ms
func Delay(delay time.Duration) Option

// DelayType set type of the delay between retries default is BackOff
func DelayType(delayType DelayTypeFunc) Option

// 内部虽然记录了 error 数组,但外层只返回记录的最后一个
func LastErrorOnly(lastErrorOnly bool) Option

// MaxDelay set maximum delay between retry does not apply by default
func MaxDelay(maxDelay time.Duration) Option

// Jitter 是抖动的意思,其实是 RandomDelay 策略下,随机的抖动
func MaxJitter(maxJitter time.Duration) Option

三、实现

3.1 Config 和 Option

核心是定义了 Config struct

type Config struct {
    attempts                      uint            // 重试几次
    attemptsForError              map[error]uint  // 各错误重试几次
    delay                         time.Duration   // 延迟多久
    maxDelay                      time.Duration   // 最多延迟多久的阈值
    maxJitter                     time.Duration   // todo 抖动是什么
    onRetry                       OnRetryFunc     // retry 时做什么
    retryIf                       RetryIfFunc     // 什么时机 retry
    delayType                     DelayTypeFunc   // todo 有什么用
    lastErrorOnly                 bool            // 只记录最后的 error
    context                       context.Context // 上下文
    timer                         Timer           // todo 貌似只有单测使用
    wrapContextErrorWithLastError bool            // todo 有什么用

    maxBackOffN uint // 最多 backoff n 次
}

然后在 Do() 或 DoWithData() 函数内会使用 opts

func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error) {
    // default 首先创建默认 struct
    config := newDefaultRetryConfig()
    
    // apply opts,然后使用 opt 函数,逐个修改公共的 *Config 变量
    for _, opt := range opts {
        opt(config)
    }
}

// 默认 struct 如下
func newDefaultRetryConfig() *Config {
    return &Config{
       attempts:         uint(10),
       attemptsForError: make(map[error]uint),
       delay:            100 * time.Millisecond,
       maxJitter:        100 * time.Millisecond,
       onRetry:          func(n uint, err error) {},
       retryIf:          IsRecoverable,
       delayType:        CombineDelay(BackOffDelay, RandomDelay), // 取 delay 之和
       lastErrorOnly:    false,
       context:          context.Background(),
       timer:            &timerImpl{},
    }
}

默认实现了,很多 Option 的闭包封装,如下:

// 一种实现, 什么也不做
func emptyOption(c *Config) {
    // 空函数体实现
}


// return the direct last error that came from the retried function
// default is false (return wrapped errors with everything)
// 闭包包围了 Option, 并返回了 Option
// 外层函数传入的 lastErrorOnly 被内层闭包函数捕获
func LastErrorOnly(lastErrorOnly bool) Option {
    return func(c *Config) {
       c.lastErrorOnly = lastErrorOnly
    }
}

// Attempts set count of retry. Setting to 0 will retry until the retried function succeeds.
// default is 10
func Attempts(attempts uint) Option {
    return func(c *Config) {
       c.attempts = attempts
    }
}

3.1.1 Delay 实现

3.1.1.1 BackOffDelay
// BackOffDelay is a DelayType which increases delay between consecutive retries
func BackOffDelay(n uint, _ error, config *Config) time.Duration {
    // 1 << 63 would overflow signed int64 (time.Duration), thus 62.
    const max uint = 62

    // 首先 maxBackOffN 默认初始值就是 0
    if config.maxBackOffN == 0 {
       if config.delay <= 0 {
          config.delay = 1
       }
       // 在此处会设置其值,上限为 62,因为 time.Duration 是有符号的 int,最多左移 62 位还能保证是正数
       config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay))))
    }

    if n > config.maxBackOffN { // 实际操作的值 n,是有上限的
       n = config.maxBackOffN
    }

    return config.delay << n // 执行左移
}
3.1.1.2 CombineDelay
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
    const maxInt64 = uint64(math.MaxInt64)

    return func(n uint, err error, config *Config) time.Duration {
       var total uint64
       for _, delay := range delays {
          total += uint64(delay(n, err, config)) // 对每个 delay() 函数求值,并求和
          if total > maxInt64 {
             total = maxInt64
          }
       }

       return time.Duration(total)
    }
}

3.1.2 Retry 策略

3.1.2.1 OnRetry

在 DoWithData() 函数中会被调用

// OnRetry function callback are called each retry
//
// log each retry example:
//
//  retry.Do(
//     func() error {
//        return errors.New("some error")
//     },
//     retry.OnRetry(func(n uint, err error) {
//        log.Printf("#%d: %s\n", n, err)
//     }),
//  )
func OnRetry(onRetry OnRetryFunc) Option {
    if onRetry == nil {
       return emptyOption
    }
    return func(c *Config) {
       c.onRetry = onRetry
    }
}

3.2 主流程 DoWithData

DoWithData 是一个泛型函数,指定的泛型 T,会被返回

func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error) {
    var n uint
    var emptyT T

    // default
    config := newDefaultRetryConfig()

    // apply opts
    for _, opt := range opts {
       opt(config)
    }

    if err := config.context.Err(); err != nil {
       return emptyT, err
    }

    // Setting attempts to 0 means we'll retry until we succeed
    var lastErr error
    if config.attempts == 0 {
       for {
          t, err := retryableFunc()
          if err == nil {
             return t, nil
          }

          if !IsRecoverable(err) {
             return emptyT, err
          }

          if !config.retryIf(err) {
             return emptyT, err
          }

          lastErr = err

          n++
          config.onRetry(n, err)
          select {
          case <-config.timer.After(delay(config, n, err)):
          case <-config.context.Done():
             if config.wrapContextErrorWithLastError {
                return emptyT, Error{config.context.Err(), lastErr}
             }
             return emptyT, config.context.Err()
          }
       }
    }

    // 主流程开始
    errorLog := Error{} // 这是一个数组, 记录了所有的错误

    // 因为后续会修改 attempts 值, 所以这里先拷贝一份, 后续使用拷贝的那一份
    attemptsForError := make(map[error]uint, len(config.attemptsForError))
    for err, attempts := range config.attemptsForError {
       attemptsForError[err] = attempts
    }

    shouldRetry := true // 当超出重试次数时, 会退出循环
    for shouldRetry {
       // 执行用户传入的主流程函数, 我们要重试的就是他
       t, err := retryableFunc()
       // 如果执行成功了, 直接返回, 不需要再重试了
       if err == nil {
          return t, nil
       }

       // 追加 error
       errorLog = append(errorLog, unpackUnrecoverable(err))

       // 用户可以自定义回调函数, 即根据返回的 err 判断是否需要重试
       if !config.retryIf(err) {
          break
       }

       // 当重试时, 需要执行的回调函数, 用户可以自定义
       config.onRetry(n, err)

       // 用户可以设置某种 err 需要重试几次. 此处会判断返回的 err 并减少需要重试的次数
       for errToCheck, attempts := range attemptsForError {
          if errors.Is(err, errToCheck) {
             attempts--
             attemptsForError[errToCheck] = attempts
             shouldRetry = shouldRetry && attempts > 0
          }
       }

       // 既然最后一次 retryableFunc() 已经执行完了, 那就不需要再等待了
       // if this is last attempt - don't wait
       if n == config.attempts-1 {
          break
       }

       select {
       case <-config.timer.After(delay(config, n, err)): // 等待一段时间后再重试
       case <-config.context.Done(): // 如果用户把 context Done() 了, 则退出即可. 通常原因是用户主动 ctx.Cancel() 或者 ctx.Timeout() 自己到达了
          if config.lastErrorOnly {
             return emptyT, config.context.Err()
          }

          return emptyT, append(errorLog, config.context.Err())
       }

       n++
       shouldRetry = shouldRetry && n < config.attempts // 总的 attempts 次数也会控制是否需要重试
    }

    if config.lastErrorOnly {
       return emptyT, errorLog.Unwrap() // 这个 errorLog 其实是一个数组, Unwrap() 其实就是返回数组的最后一项
    }
    return emptyT, errorLog
}

3.2.1 Do 封装函数

其实 Do() 就是把 DoWithData() 的 retryableFuncWithData() 封装了一层

func Do(retryableFunc RetryableFunc, opts ...Option) error {
    retryableFuncWithData := func() (any, error) {
       // 执行 retryableFunc() 会返回 error
       // 再封装一个 any, 连同 error 一起返回
       return nil, retryableFunc()
    }

    _, err := DoWithData(retryableFuncWithData, opts...)
    return err
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1495755.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【智能家居】东胜物联ODM定制ZigBee网关,助力能源管理解决方案商,提升市场占有率

背景 本文案例服务的客户是专业从事智能家居能源管理的解决方案商&#xff0c;其产品与服务旨在帮助用户监测、管理和优化能源消耗&#xff0c;以提高能源使用效率。 随着公司的扩张&#xff0c;为了增加市场占有率&#xff0c;他们希望找到更好的硬件服务支持&#xff0c;以…

染色法

染色法&#xff1a;将图中所有的节点都染上颜色1或颜色2&#xff0c;染色规则为相邻节点不能为同一种颜色&#xff0c;即节点 x 若为颜色1则它的相邻节点 y 的颜色为2 时间复杂度&#xff1a;O(nm) 解决问题&#xff1a;判断是否为二分图 二分图&#xff1a;又称作二部图&am…

java之——反射

文章目录 反射的基本概念反射的概念反射的作用 反射的基本信息反射的相关类型Class类Class类中的相关方法newInstabce()getName方法 Field类中的方法getFiled()方法getDeclareField(String name) Constructor类getConstructors()getDeclaredConstructors() Method类getMethods(…

STM32使用标准库编写外部时钟控制oled显示计数(proteus仿真)

这节课的结果是在上节课的基础上更改的&#xff1a;电路图为&#xff1a;用一个开关来模拟外部时钟的高低电平的变化。 当然也可以配置一个外部时钟来模拟&#xff0c;也是可以的&#xff1a; 由于这节课的代码是在上节课的基础上有一点修改而来的&#xff0c;所以就只把更改的…

8、Linux-软件安装:rpm和yum;配置yum阿里云镜像源

一、介绍 Linux安装软件有两种方式&#xff0c; ①rpm&#xff1a;安装已有的安装包&#xff0c;类似于Windows中双击exe的安装包程序 ②yum&#xff1a;拉取远程仓库的文件&#xff0c;类似于python的pip install 区别&#xff1a;假设软件A依赖软件B&#xff0c;软件B依赖…

个推与华为深度合作,成为首批支持兼容HarmonyOS NEXT的服务商

自华为官方宣布HarmonyOS NEXT鸿蒙星河版开放申请以来&#xff0c;越来越多的头部APP宣布启动鸿蒙原生开发&#xff0c;鸿蒙生态也随之进入全新发展的第二阶段。 作为华为鸿蒙生态的重要合作伙伴&#xff0c;个推一直积极参与鸿蒙生态建设。为帮助用户在HarmonyOS NEXT上持续享…

git同步指定分支指定文件,可同步到同一仓库,可同步到不同仓库

源代码&#xff1a; run-func.js const express require(express); const fs require(fs); const simpleGit require(simple-git); const cors require(cors); // 引入 cors 模块const app express();// 使用 cors 中间件 app.use(cors());const git simpleGit().cwd(..…

OSPF NSSA实验简述

OSPF NSSA实验简述 1、OSPF NSSA区域配置 为解决末端区域维护过大LSDB带来的问题&#xff0c;通过配置stub 区域或totally stub区域可以解决&#xff0c;但是他们都不能引入外部路由场景。 No so stuby area &#xff08;区域&#xff09;NSSA 可以引入外部路由&#xff0c;支持…

C++小记 -链表

链表 文章目录 链表链表基础理论链表的类型单链表双链表循环链表 链表的存储方式链表的定义链表的操作添加节点删除节点 性能分析构建链表删除节点&#xff08;内存泄漏的坑&#xff09;1.直接移除2.使用虚拟头结点3.delete指针后&#xff0c;要将指针置为NULL&#xff01;&…

【python】对角线遍历

python系列文章目录 【python】基于cv2提取图片上的文本内容 【python】简单作图 【python】数组字符串等实用 【python】sort与sorted排序使用 【python】对角线遍历 python系列文章目录说明1.分析2.注意事项2.1 遍历2.2 区间2.3 顺序 3.代码实现 说明 给你一个大小为 m x n…

SpringCloud Feign实现微服务间的远程调用(黑马头条Day04)

目录 Feign介绍 Feign的执行流程 项目中使用Feign介绍 实现步骤 添加Feign依赖 注册feign远程调用接口&#xff0c;并指定需要调用的微服务 在leadnews-artilce微服务中创建接口实现类 在wemedia微服务中添加EnableFeignClients注解&#xff0c;并指定需要扫描的包 在wem…

突破编程_前端_JS编程实例(简单树结构组件)

1 开发目标 实现如下简单树结构组件&#xff1a; 再点击树节点后&#xff0c;会调用客户端传入的回调函数&#xff1a; 2 详细需求 简单树结构组件需根据客户端提供的参数创建&#xff0c;具备动态构建树形结构节点、选项卡切换及自定义内容显示等功能&#xff1a; &#xf…

R语言生物群落(生态)数据统计分析与绘图教程

原文链接&#xff1a;R语言生物群落&#xff08;生态&#xff09;数据统计分析与绘图教程 前沿 R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂&#xff0c;涉及众多统计分析方法。 第一 R基础及数据准备 一&#xff1a;R和R…

【树上倍增】【割点】 【换根法】3067. 在带权树网络中统计可连接服务器对数目

作者推荐 视频算法专题 本文涉及知识点 树上倍增 树 图论 并集查找 换根法 深度优先 割点 LeetCode3067. 在带权树网络中统计可连接服务器对数目 给你一棵无根带权树&#xff0c;树中总共有 n 个节点&#xff0c;分别表示 n 个服务器&#xff0c;服务器从 0 到 n - 1 编号…

快速了解Redis

Redis是什么&#xff1f; Redis是一个数据库&#xff0c;是一个跨平台的非关系型数据库&#xff0c;Redis完全开源&#xff0c;遵守BSD协议。它通过键值对(Key-Value)的形式存储数据。 它与mysql数据库有什么区别&#xff1f; redis通过键值对(Key-Value)的形式存储数据&…

深入理解 Vuex:从基础到应用场景

前言 在之前的文章中&#xff0c;我们已经对 Vue.js 有了一定的了解。今天我们要对Vue官方的状态共享管理器Vuex进行详细讲解&#xff0c;将其基本吃透&#xff0c;目标是面对大多数业务需求&#xff1b; 一、介绍 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用…

Rust入门:GCC或VS2019中的c或c++程序如何调用Rust静态库

首先创建一个rust的库&#xff0c;这里我假设命名为c-to-rust1 cargo new --lib c-to-rust1 其中&#xff0c;src/lib.rs的内容如下&#xff0c; #[no_mangle] pub extern "C" fn get_string() -> *const u8 {b"Hello C World\0".as_ptr() }注解 …

高分辨率全球海洋温度和盐度再分析数据Global Ocean Physics Reanalysis(0.083°),并利用matlab读取绘图

1.引言 在研究全球海平面变化的问题中&#xff0c;卫星测高获得总的海平面变化&#xff0c;而海平面变化包含质量变化和比容变化。因此测高数据和海洋物理分析数据对于海平面研究至关重要。 测高数据下载网址&#xff1a; Global Ocean Gridded L 4 Sea Surface Heights And …

【深度学习笔记】计算机视觉——FCN(全卷积网络

全卷积网络 sec_fcn 如 :numref:sec_semantic_segmentation中所介绍的那样&#xff0c;语义分割是对图像中的每个像素分类。 全卷积网络&#xff08;fully convolutional network&#xff0c;FCN&#xff09;采用卷积神经网络实现了从图像像素到像素类别的变换 :cite:Long.Sh…

Docker数据卷的挂载

目录 1 概念 2 常用命令 3 操作步骤(主要讲在创建容器时的挂载) 3.1 挂载在默认目录 3.2 挂载在自定义目录 4 附加内容(查看容器的挂载情况) 1 概念 数据卷&#xff08;volume&#xff09;是一个虚拟目录&#xff0c;是容器内目录与宿主机目录之间映射的桥梁。这样容器内…