重试控制策略
重试控制策略
Kubernetes 提供了一些控制重试策略来处理与 API 交互时可能发生的错误和故障
这些策略可以帮助实现可靠的操作,提高应用程序的容错性
-
重试
- Retry 策略在操作失败时会立即进行重试
- 控制器或客户端可以指定重试次数,如果操作在重试次数内仍然失败,则放弃重试并返回错误
-
指数退避(Exponential Backoff)
- Exponential Backoff 策略会根据重试次数逐渐增加重试的时间间隔
- 控制器或客户端可以指定初始的重试间隔和最大的重试间隔
- 重试间隔会根据指数函数逐渐增加,例如每次重试的间隔时间是前一次的两倍
- 这样的策略有助于避免在操作失败时产生过多的请求,减少对系统的负载压力
-
等待(Wait)
- Wait 策略会在操作失败时等待一段固定的时间间隔,然后再进行重试
- 控制器或客户端可以指定等待的时间间隔
常用函数
包名
源码: https://github.com/kubernetes/client-go/blob/master/util/retry/util.go
OnError
// 创建一个自定义的退避策略
waitBackoff := wait.Backoff{
Duration: 2 * time.Second, // 初始退避间隔
Factor: 2, // 退避系数
Jitter: 0.1, // 随机化因子
Steps: 10, // 最大重试次数
}
使用 wait.Backoff{}
结构体时,各个字段的含义如下:
Duration: 2 * time.Second
:初始退避间隔是 2 秒。在进行第一次重试之前,会等待 2 秒钟,这是重试的初始等待时间Factor: 2
:退避系数是 2。每次重试时,会将上一次的等待时间乘以 2,以获得下一次重试的等待时间。例如,如果第一次重试等待了 2 秒,那么下一次将等待 4 秒,再下一次将等待 8 秒,依此类推Jitter: 0.1
:随机化因子是 0.1。为了避免所有重试操作同时发生,可以在每次计算等待时间时引入一定的随机性。随机化因子表示在计算等待时间时,会将结果乘以一个介于 0 和 1 之间的随机值。这样可以使得每个重试操作之间有一定的随机差异Steps: 5
:最大重试次数是 5。会在达到最大重试次数后停止重试操作。在这种情况下,即使上一次重试失败,也不会再进行下一次重试
假设有一个需要重试的操作。初始退避间隔为 2 秒,然后每次重试的等待时间都会翻倍。为了避免同时进行所有重试操作,会在每次计算等待时间时引入一定的随机性。最大重试次数为 5,如果达到最大重试次数后仍然失败,将停止重试
package main
import (
"fmt"
"math/rand"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
)
var numberOfRetries = 0
func main() {
// 创建一个自定义的退避策略
waitBackoff := wait.Backoff{
Duration: 2 * time.Second, // 初始退避间隔
Factor: 2, // 退避系数
Jitter: 0.1, // 随机化因子
Steps: 10, // 最大重试次数
}
// 自定义的退避策略函数
backoffFunc := func() error {
klog.Infof("Performing operation...")
err := performOperation()
if err != nil {
klog.Warningf("Operation failed: %v", err)
return err
}
klog.Infof("Operation succeeded.")
return nil
}
// 使用自定义的退避策略执行操作
err := retry.OnError(waitBackoff, shouldRetry, backoffFunc)
if err != nil {
klog.Errorf("Failed to perform operation: %v", err)
} else {
klog.Infof("Operation completed successfully.")
}
}
// 执行操作的函数
func performOperation() error {
numberOfRetries++
klog.Infof(fmt.Sprintf("Start %d retry", numberOfRetries))
// 模拟操作失败的情况
if rand.Intn(10) < 9 {
return fmt.Errorf("Operation failed")
}
return nil
}
// 判断是否应该重试的函数
func shouldRetry(err error) bool {
// 这里可以根据具体的错误类型进行判断
return true
}
RetryOnConflict
RetryOnConflict
函数的作用是在发生冲突错误(Conflict Error)时进行重试
函数接受两个参数:
backoff
:一个实现了wait.Backoff
接口的对象,用于定义最大重试次数和两次重试之间的等待间隔fn
:一个函数,表示要执行的操作函数
在函数内部,RetryOnConflict
调用了 OnError
函数,并传入了 errors.IsConflict
函数作为 retriable
参数。errors.IsConflict
是一个用于判断错误是否为冲突错误的函数
OnError
函数会在每次重试时调用 fn
函数执行操作,并根据返回的错误判断是否可重试。而将 errors.IsConflict
作为 retriable
参数,意味着只有当返回的错误是冲突错误时,才会进行重试
通过使用 RetryOnConflict
函数,可以在执行某个操作时遇到冲突错误时自动进行重试,直到操作成功或达到最大重试次数。这样可以处理并发访问资源时可能发生的冲突情况,提高操作的成功率
示例代码:
retryErr := retry.RetryOnConflict(
retry.DefaultRetry,
func() error {
user, err := authClient.Users().Get(context.TODO(), req.Username, metav1.GetOptions{})
if err != nil {
return err
}
newUser := user.DeepCopy()
newUser.Status.AuditLog.CreateTime = currentTimeStr
_, err = authClient.Users().UpdateStatus(context.TODO(), newUser, metav1.UpdateOptions{})
return err
},
)
// // DefaultRetry is the recommended retry for a conflict where multiple clients
// // are making changes to the same resource.
// var DefaultRetry = wait.Backoff{
// Steps: 5,
// Duration: 10 * time.Millisecond,
// Factor: 1.0,
// Jitter: 0.1,
// }
Conflict Error:
Conflict Error
在Kubernetes中,冲突错误(Conflict Error)通常指的是针对资源对象的更新操作时发生的冲突情况
当多个并发的操作尝试修改同一个资源对象时,可能会发生冲突。例如,两个操作同时尝试更新同一个资源的字段,或者一个操作在另一个操作完成之前修改了资源的状态
当这种冲突发生时,Kubernetes会返回一个冲突错误。这个错误通常是HTTP状态码为 409(Conflict)的响应。它表示当前的操作与其他操作存在冲突,无法顺利执行
冲突错误的常见原因包括:
- 资源版本不匹配:当多个操作使用相同的资源版本号来修改同一个资源时,会导致冲突
- 并发更新:当多个操作同时尝试更新同一个资源的字段或状态时,可能会发生冲突
- 乐观并发控制:某些资源对象使用乐观并发控制机制,即在更新时会比较资源的版本号,如果版本号不匹配,则表示发生了冲突