天天看点

写好operator之重试控制策略

作者:云原生驿站

Kubernetes 提供了一些控制重试策略来处理与 API 交互时可能发生的错误和故障。这些策略可以帮助您实现可靠的操作,提高应用程序的容错性。

  1. 重试:
    • Retry

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

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

      策略会在操作失败时等待一段固定的时间间隔,然后再进行重试。
    • 控制器或客户端可以指定等待的时间间隔。
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
}
           

使用

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,如果达到最大重试次数后仍然失败,我们将停止重试。

如果想提高模拟测试的时候,程序在中间成功的概率的话,可以修改这里的值
if rand.Intn(10) < 9 {
return fmt.Errorf("Operation failed")
}
           

除了

retry.OnError

还有

retry.RetryOnConflict

RetryOnConflict

函数的作用是在发生冲突错误(Conflict Error)时进行重试。

函数接受两个参数:

  • backoff

    :一个实现了

    wait.Backoff

    接口的对象,用于定义最大重试次数和两次重试之间的等待间隔。
  • fn

    :一个函数,表示要执行的操作函数。

在函数内部,

RetryOnConflict

调用了

OnError

函数,并传入了

errors.IsConflict

函数作为

retriable

参数。

errors.IsConflict

是一个用于判断错误是否为冲突错误的函数。

OnError

函数会在每次重试时调用

fn

函数执行操作,并根据返回的错误判断是否可重试。而将

errors.IsConflict

作为

retriable

参数,意味着只有当返回的错误是冲突错误时,才会进行重试。

通过使用

RetryOnConflict

函数,我们可以在执行某个操作时遇到冲突错误时自动进行重试,直到操作成功或达到最大重试次数。这样可以处理并发访问资源时可能发生的冲突情况,提高操作的成功率。

何为Conflict Error?

在Kubernetes中,冲突错误(Conflict Error)通常指的是针对资源对象的更新操作时发生的冲突情况。

当多个并发的操作尝试修改同一个资源对象时,可能会发生冲突。例如,两个操作同时尝试更新同一个资源的字段,或者一个操作在另一个操作完成之前修改了资源的状态。

当这种冲突发生时,Kubernetes会返回一个冲突错误。这个错误通常是HTTP状态码为 409(Conflict)的响应。它表示当前的操作与其他操作存在冲突,无法顺利执行。

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

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

继续阅读