不断重试 Golang 中的函数

new*_*ing 1 go exponential-backoff retry-logic

我正在尝试创建一个可以按以下方式工作的功能:

  1. 一旦服务函数被调用,它就会使用 Fetch 函数从服务获取记录(以字节数组的形式),JSON 解组字节数组,填充结构,然后将结构发送到 DB 函数保存到数据库。
  2. 现在,由于这需要是一个连续的工作,所以我添加了两个 if 条件,如果收到的记录长度为 0,则我们使用重试函数重试拉取记录,否则我们只写入数据库。

我已经尝试调试重试函数有一段时间了,但它不起作用,并且在第一次重试后基本上停止了(即使我将尝试次数指定为 100)。我可以做什么来确保它不断重试拉取记录?

代码如下:

// RETRY FUNCTION
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; ; i++ {
    err = f()
    if err == nil {
        return
    }

    if i >= (attempts - 1) {
        break
    }

    time.Sleep(sleep)
    sleep *= 2

    log.Println("retrying after error:", err)
}
return fmt.Errorf("after %d attempts, last error: %s", attempts, err) }


//Save Data function 

type Records struct {
Messages [][]byte
}

func (s *Service) SaveData(records Records, lastSentPlace uint) error {

//lastSentPlace is sent as 0 to begin with.
for i := lastSentPlace; i <= records.Place-1; i++ {

    var msg Records
    msg.Unmarshal(records.Messages[i])

    order := MyStruct{
        Fruit:    msg.Fruit,
        Burger:   msg.Burger,
        Fries:    msg.Fries,
     }

    err := s.db.UpdateOrder(context.TODO(), nil , order)
    if err != nil {
        logging.Error("Error occured...")
    }
    
}return nil}



//Service function (This runs as a batch, which is why we need retrying)

func (s *Service) MyServiceFunction(ctx context.Context, place uint, length uint) (err error) {

var lastSentPlace = place

records, err := s.Poll(context.Background(), place, length)
if err != nil {
    logging.Info(err)
}

// if no records found then retry.
if len(records.Messages) == 0 {
    
    err = retry(100, 2*time.Minute, func() (err error) {
        records, err := s.Poll(context.Background(), place, length)
        
        // if data received, write to DB
        if len(records.Messages) != 0 {
            err = s.SaveData(records, lastSentPlace)
        }
        return
    })
    // if data is not received, or if err is not null, retry
    if err != nil || len(records.Messages) == 0 {
        log.Println(err)
        return
    }
// if data received on first try, then no need to retry, write to db 
} else if len(records.Messages) >0 {
    err = s.SaveData(records, lastSentPlace)
    if err != nil {
        return err
    }
}

return nil }
Run Code Online (Sandbox Code Playgroud)

我认为,问题在于我尝试实现重试功能的方式,我已经尝试调试它有一段时间了,但是作为该语言的新手,我真的陷入了困境。我想做的是,如果没有找到记录,则实施退避。任何帮助是极大的赞赏。

谢谢 !!!

小智 16

我做了一个更简单的重试。

  • 使用更简单的 for 循环逻辑来确保正确性。
  • 我们在执行重试之前休眠,因此将其用作i > 0休眠的条件。

这是代码:

func retry(attempts int, sleep time.Duration, f func() error) (err error) {
    for i := 0; i < attempts; i++ {
        if i > 0 {
            log.Println("retrying after error:", err)
            time.Sleep(sleep)
            sleep *= 2
        }
        err = f()
        if err == nil {
            return nil
        }
    }
    return fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}
Run Code Online (Sandbox Code Playgroud)


小智 6

我知道这是一个老问题,但在搜索重试时遇到它并将其用作解决方案的基础。

该版本可以接受具有 2 个返回值的 func,并使用 golang 1.18 中的泛型来实现这一点。我在 1.17 中尝试过,但无法找到使该方法通用的方法。

这可以扩展到任何类型的任意数量的返回值。我在这里使用过any,但这可能仅限于一系列类型。

func retry[T any](attempts int, sleep int, f func() (T, error)) (result T, err error) {
    for i := 0; i < attempts; i++ {
        if i > 0 {
            log.Println("retrying after error:", err)
            time.Sleep(time.Duration(sleep) * time.Second)
            sleep *= 2
        }
        result, err = f()
        if err == nil {
            return result, nil
        }
    }
    return result, fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

var config Configuration

something, err := retry(config.RetryAttempts, config.RetrySleep, func() (Something, error) { return GetSomething(config.Parameter) })

func GetSomething(parameter string) (something Something, err error) {
    // Do something flakey here that might need a retry...
    return something, error
}
Run Code Online (Sandbox Code Playgroud)

希望对与我有相同用例的人有所帮助。