继续重试 Golang 中的函数
Keep retrying a function in Golang
我正在尝试创建一个可以按以下方式工作的功能:
- 调用服务函数后,它会立即使用 Fetch 函数从服务中获取记录(以字节数组的形式出现),JSON解组字节数组,填充结构并然后将结构发送到数据库函数以保存到数据库。
- 现在,因为这需要是一个连续的工作,我添加了两个 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 }
我认为,问题出在我尝试实现重试功能的方式上,我已经尝试调试了一段时间,但作为这门语言的新手,我真的被困住了。我想做的是,如果没有找到记录,则实施退避。非常感谢任何帮助。
谢谢!!!
重试机制有一个库。
https://github.com/avast/retry-go
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
fmt.Println(body)
我做了一个更简单的重试。
- 使用更简单的循环逻辑来确保正确性。
- 我们在执行重试之前休眠,所以使用
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)
}
您调用的函数正在使用上下文。因此,处理该上下文很重要。
如果您不知道上下文是什么以及如何使用它,我建议您 post:https://blog.golang.org/context
您的重试函数还应该处理上下文。为了让你走上正轨,我给你一个简单的实现。
func retryMyServiceFunction(ctx context.Context, place uint, length uint, sleep time.Duration) {
for {
select {
case ctx.Done():
return
default:
err := MyServiceFunction(ctx, place, length)
if err != nil {
log.Println("handle error here!", err)
time.Sleep(sleep)
} else {
return
}
}
}
}
我不喜欢睡眠部分。所以你应该分析返回的错误。您还必须考虑超时。当您让服务休眠时间过长时,可能会超时。
GoPlayground 在已接受答案的评论中,有一些我会考虑添加的内容。通过不使用 if i > 0 {
语句,在 for 循环中使用 continue 和 break 将使循环更加简单。此外,我会在所有函数中使用早期 return 来直接 return 出错。最后我会一直使用错误来检查函数是否失败,检查值的有效性应该在执行的函数本身内部。
这将是我的小尝试:
package main
import (
"errors"
"fmt"
"log"
"time"
)
func main() {
var complicatedFunctionPassing bool = false
var attempts int = 5
// if complicatedFunctionPassing is true retry just makes one try
// if complicatedFunctionPassing is false retry makes ... attempts
err := retry(attempts, time.Second, func() (err error) {
if !complicatedFunctionPassing {
return errors.New("somthing went wrong in the important function")
}
log.Println("Complicated function passed")
return nil
})
if err != nil {
log.Printf("failed after %d attempts with error: %s", attempts, err.Error())
}
}
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
fmt.Println("This is attempt number", i+1)
// calling the important function
err = f()
if err != nil {
log.Printf("error occured after attempt number %d: %s", i+1, err.Error())
log.Println("sleeping for: ", sleep.String())
time.Sleep(sleep)
sleep *= 2
continue
}
break
}
return err
}
你可以在这里试试:
https://go.dev/play/p/Ag8ObCb980U
我知道这是一个老问题,但在搜索重试时遇到它并将其用作解决方案的基础。
此版本可以接受具有 2 个 return 值的函数,并在 golang 1.18 中使用泛型来实现这一点。我在 1.17 中尝试过,但找不到使该方法通用的方法。
这可以扩展到任意数量的任意类型的 return 值。我在这里使用了 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)
}
用法示例:
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
}
希望对与我有相同用例的人有所帮助。
我正在尝试创建一个可以按以下方式工作的功能:
- 调用服务函数后,它会立即使用 Fetch 函数从服务中获取记录(以字节数组的形式出现),JSON解组字节数组,填充结构并然后将结构发送到数据库函数以保存到数据库。
- 现在,因为这需要是一个连续的工作,我添加了两个 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 }
我认为,问题出在我尝试实现重试功能的方式上,我已经尝试调试了一段时间,但作为这门语言的新手,我真的被困住了。我想做的是,如果没有找到记录,则实施退避。非常感谢任何帮助。
谢谢!!!
重试机制有一个库。 https://github.com/avast/retry-go
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
fmt.Println(body)
我做了一个更简单的重试。
- 使用更简单的循环逻辑来确保正确性。
- 我们在执行重试之前休眠,所以使用
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)
}
您调用的函数正在使用上下文。因此,处理该上下文很重要。
如果您不知道上下文是什么以及如何使用它,我建议您 post:https://blog.golang.org/context
您的重试函数还应该处理上下文。为了让你走上正轨,我给你一个简单的实现。
func retryMyServiceFunction(ctx context.Context, place uint, length uint, sleep time.Duration) {
for {
select {
case ctx.Done():
return
default:
err := MyServiceFunction(ctx, place, length)
if err != nil {
log.Println("handle error here!", err)
time.Sleep(sleep)
} else {
return
}
}
}
}
我不喜欢睡眠部分。所以你应该分析返回的错误。您还必须考虑超时。当您让服务休眠时间过长时,可能会超时。
GoPlayground 在已接受答案的评论中,有一些我会考虑添加的内容。通过不使用 if i > 0 {
语句,在 for 循环中使用 continue 和 break 将使循环更加简单。此外,我会在所有函数中使用早期 return 来直接 return 出错。最后我会一直使用错误来检查函数是否失败,检查值的有效性应该在执行的函数本身内部。
这将是我的小尝试:
package main
import (
"errors"
"fmt"
"log"
"time"
)
func main() {
var complicatedFunctionPassing bool = false
var attempts int = 5
// if complicatedFunctionPassing is true retry just makes one try
// if complicatedFunctionPassing is false retry makes ... attempts
err := retry(attempts, time.Second, func() (err error) {
if !complicatedFunctionPassing {
return errors.New("somthing went wrong in the important function")
}
log.Println("Complicated function passed")
return nil
})
if err != nil {
log.Printf("failed after %d attempts with error: %s", attempts, err.Error())
}
}
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
fmt.Println("This is attempt number", i+1)
// calling the important function
err = f()
if err != nil {
log.Printf("error occured after attempt number %d: %s", i+1, err.Error())
log.Println("sleeping for: ", sleep.String())
time.Sleep(sleep)
sleep *= 2
continue
}
break
}
return err
}
你可以在这里试试: https://go.dev/play/p/Ag8ObCb980U
我知道这是一个老问题,但在搜索重试时遇到它并将其用作解决方案的基础。
此版本可以接受具有 2 个 return 值的函数,并在 golang 1.18 中使用泛型来实现这一点。我在 1.17 中尝试过,但找不到使该方法通用的方法。
这可以扩展到任意数量的任意类型的 return 值。我在这里使用了 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)
}
用法示例:
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
}
希望对与我有相同用例的人有所帮助。