跳转至

重试控制策略

重试控制策略

Kubernetes 提供了一些控制重试策略来处理与 API 交互时可能发生的错误和故障

这些策略可以帮助实现可靠的操作,提高应用程序的容错性

  1. 重试

    • Retry 策略在操作失败时会立即进行重试
    • 控制器或客户端可以指定重试次数,如果操作在重试次数内仍然失败,则放弃重试并返回错误
  2. 指数退避(Exponential Backoff)

    • Exponential Backoff 策略会根据重试次数逐渐增加重试的时间间隔
    • 控制器或客户端可以指定初始的重试间隔和最大的重试间隔
    • 重试间隔会根据指数函数逐渐增加,例如每次重试的间隔时间是前一次的两倍
    • 这样的策略有助于避免在操作失败时产生过多的请求,减少对系统的负载压力
  3. 等待(Wait)

    • Wait 策略会在操作失败时等待一段固定的时间间隔,然后再进行重试
    • 控制器或客户端可以指定等待的时间间隔

常用函数

包名

import "k8s.io/client-go/util/retry"

源码: 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)的响应。它表示当前的操作与其他操作存在冲突,无法顺利执行

冲突错误的常见原因包括:

  • 资源版本不匹配:当多个操作使用相同的资源版本号来修改同一个资源时,会导致冲突
  • 并发更新:当多个操作同时尝试更新同一个资源的字段或状态时,可能会发生冲突
  • 乐观并发控制:某些资源对象使用乐观并发控制机制,即在更新时会比较资源的版本号,如果版本号不匹配,则表示发生了冲突