workqueue

本文深入探讨Kubernetes中workqueue的工作原理,包括其接口、类型、方法及使用场景。详解了RateLimiter接口的不同实现,如BucketRateLimiter、ItemExponentialFailureRateLimiter等,以及它们如何与workqueue结合,确保资源处理的效率和公平性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

workqueue

workqueue 提供了实现 RateLimitingInterface 接口的 Queue,它支持如下特性:

  1. 顺序性(Fair):item 处理的顺序和添加它的顺序一致;
  2. 单实例(Stingy):一个 item 只会被一个 worker 处理,如果在被处理前添加了多次,则只会被处理一次;
  3. 在处理过程中,可以将 item 重新入队列,后续可以重新处理;
  4. 关闭通知;

在实现 K8S Controller 时,写入 workqueue 的 item 是资源对象的标识 Key,而非资源对象本身(因为对象本身一般都在变化)。

Interface 接口定义了 queue 接口。

RateLimiter 接口的 When(item interface{}) time.Duration 方法,返回对某个对象进行操作的延迟。

DelayingInterface 接口包含 Interface 接口,但是提供了额外的 AddAfter(item interface{}, duration time.Duration) 方法,其中的 duration 一般来源于 RateLimiter.When() 方法。

RateLimitingInterface 接口包含 DelayingInterface 接口,但是提供了额外的、操作 RateLimiter 的 AddRateLimited(item interface{})、Forget(item interface{})、NumRequeues(item interface{}) int 方法。

函数 NewRateLimitingQueue() 返回实现 RateLimitingInterface 接口的对象,它的参数是实现 RateLimter 接口的对象。

Interface 接口

Interface 接口定义了 queue 接口。

// 来源于:k8s.io/client-go/util/workqueue/queue.go
type Interface interface {
	Add(item interface{})
	Len() int
	Get() (item interface{}, shutdown bool)
	Done(item interface{})
	ShutDown()
	ShuttingDown() bool
}

函数 New()、NewNamed()、newQueue() 均返回实现 Interface 接口的 Type 类型对象:

// 来源于:k8s.io/client-go/util/workqueue/queue.go
func New() *Type {
	return NewNamed("")
}

func NewNamed(name string) *Type {
	rc := clock.RealClock{}
	return newQueue(
		rc,
		globalMetricsFactory.newQueueMetrics(name, rc),
		defaultUnfinishedWorkUpdatePeriod,
	)
}

func newQueue(c clock.Clock, metrics queueMetrics, updatePeriod time.Duration) *Type {
	t := &Type{
		clock:                      c,
		dirty:                      set{},
		processing:                 set{},
		cond:                       sync.NewCond(&sync.Mutex{}),
		metrics:                    metrics,
		unfinishedWorkUpdatePeriod: updatePeriod,
	}
	go t.updateUnfinishedWorkLoop()
	return t
}

实现 Interface 接口的 Type 类型

Type 类型实现了 Interface 接口,即实现了 work queue。

// 来源于:k8s.io/client-go/util/workqueue/queue.go
type Type struct {
    // queue 是一个将要处理的 item 有序列表,item 顺序和加入的顺序一致;
    // queue 中的原生也位于 dirty 中,但是不能位于 processing 中。
	queue []t

    // dirty 包含所有正在处理的、即将被处理的 item
	dirty set

    // processing 包含正在处理的 item,该 item 可能同时位于 dirty 中
    // 当处理 item 结束时,如果发现 dirty 中有该 item,如果有的话,将它加到 queue 中
	processing set

	cond *sync.Cond

	shuttingDown bool

	metrics queueMetrics

	unfinishedWorkUpdatePeriod time.Duration
	clock                      clock.Clock
}

q.dirty 缓存了所有 item,q.processing 缓存了正在处理的 item,而 q.queue 缓存了即将被处理的 item。

Add() 方法

Add() 方法用于向 workqueue 中添加 item,但是在添加前,它会检查 queue 中是否有该 item,或 item 是否在处理过程中:

  1. 如果 q.dirty 中有该 item,则直接返回;
  2. 否则将 item 添加到 q.dirty 中;
  3. 如果 item 位于 q.processing 中,则表示正在处理,则直接返回;
  4. 否则,将 item 添加到 q.queue 中;
// 来源于:k8s.io/client-go/util/workqueue/queue.go
func (q *Type) Add(item interface{}) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	if q.shuttingDown {
		return
	}
	if q.dirty.has(item) {
		return
	}

	q.metrics.add(item)

	q.dirty.insert(item)
	if q.processing.has(item) {
		return
	}

	q.queue = append(q.queue, item)
	q.cond.Signal()
}
Get() 方法

Get() 方法从 queue 中返回一个 item:

  1. 从 q.queue 中返回第一个 item;
  2. 将该 item 插入到 q.processing 中,表示正在处理;
  3. 将该 item 从 q.dirty 删除;

Get() 方法将 item 从 q.queue 中弹出,插入 q.processing,从 q.dirty 中删除。

// 来源于:k8s.io/client-go/util/workqueue/queue.go
func (q *Type) Get() (item interface{}, shutdown bool) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	for len(q.queue) == 0 && !q.shuttingDown {
		q.cond.Wait()
	}
	if len(q.queue) == 0 {
		// We must be shutting down.
		return nil, true
	}

	item, q.queue = q.queue[0], q.queue[1:]

	q.metrics.get(item)

	q.processing.insert(item)
	q.dirty.delete(item)

	return item, false
}
Done() 方法

Done() 方法表示对 item 的处理结束,将它从 q.processing 移除。同时查看 q.dirty 中是否有该 item,如果有的话,将它加入 q.queue,供后续 Get() 返回处理:

注意:处理结束后必须调用 Done() 方法,否则对象一致处于 q.processing 中,后续 Add() 该 item 可能会失效(只会成功一次,添加到 q.dirty 中,后续都失效)。

// 来源于:k8s.io/client-go/util/workqueue/queue.go
func (q *Type) Done(item interface{}) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()

	q.metrics.done(item)

	q.processing.delete(item)
	if q.dirty.has(item) {
		q.queue = append(q.queue, item)
		q.cond.Signal()
	}
}
向 workqueue 添加 item 的 4 种情况

第一次添加 item:

  1. 添加到 q.dirty 中;
  2. q.processing 中没有该 item,故添加 q.queue 中;

如果该 item 没有被 Get() 返回,也没有正在被处理,再次添加 item:

  1. q.dirty 中有该对象,直接返回;(无效添加)

如果该 item 被 Get() 返回,但是在处理过程中,再次添加 item:

  1. q.dirty 中没有该 item,故添加到 q.dirty 中;
  2. q.processing 中有该 item,故直接返回(不添加到 q.queue 中);

如果该 item 被 Get() 返回,且处理结束(调用 Done 方法),再次添加 item:

  1. 和第一次添加该 time 的效果一致。

对于第三种情况,即处理过程中再次添加该 item,在处理结束调用 Done() 方法的过程中,会将 itme 从 q.dirty 中移除,添加到 q.queue 中,这样后续可以 Get() 处理。

Type 类型的 workqueue 一般不会单独使用,而是内嵌到 deleyQueue 中,和 rateLimiter 联合使用。

RateLimiter 接口

RateLimiter 接口的 When() 方法,返回了下一次添加 item 需要等待的时间

// 来源于:k8s.io/client-go/util/workqueue/default_rate_limiters.go
type RateLimiter interface {
	// When gets an item and gets to decide how long that item should wait
	When(item interface{}) time.Duration
	// Forget indicates that an item is finished being retried.  Doesn't matter whether its for perm failing
	// or for success, we'll stop tracking it
	Forget(item interface{})
	// NumRequeues returns back how many failures the item has had
	NumRequeues(item interface{}) int
}

函数 DefaultControllerRateLimiter() 返回实现该接口的 NewMaxOfRateLimiter 类型对象。

// 来源于:k8s.io/client-go/util/workqueue/default_rate_limiters.go
func DefaultControllerRateLimiter() RateLimiter {
	return NewMaxOfRateLimiter(
		NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second),
		// 10 qps, 100 bucket size.  This is only for retry speed and its only the overall factor (not per item)
		&BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
	)
}

在开发自定义 Controller 时,经常使用该函数返回的 RateLimiter 来创建 workqueue:

  1. BucketRateLimiter 实现 overall rate limiting,NewItemExponentialFailureRateLimiter 实现 per-item 的 rate limiting;
  2. overall 是 token bucket,per-item 是 exponential;

实现 RateLimter 接口的 BucketRateLimiter 类型

BucketRateLimiter 是基于标准的 token bucket 实现的限速器 :

// 来源于:k8s.io/client-go/util/workqueue/default_rate_limiters.go
type BucketRateLimiter struct {
	*rate.Limiter
}

var _ RateLimiter = &BucketRateLimiter{}

func (r *BucketRateLimiter) When(item interface{}) time.Duration {
	return r.Limiter.Reserve().Delay()
}

func (r *BucketRateLimiter) NumRequeues(item interface{}) int {
	return 0
}

func (r *BucketRateLimiter) Forget(item interface{}) {
}

创建 BucketRateLimiter(参考上面的 DefaultControllerRateLimiter() 函数):

BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}

这会创建一个 10qps、100 bucket size 的 token bucket 限速器。该类型的限速器,如果 bucket size 有富余,则允许 burst 大于 10qps。

实现 RateLimter 接口的 ItemExponentialFailureRateLimiter 类型

ItemExponentialFailureRateLimiter 实现了根据失败次数对延迟进行指数型增长的限速器,延迟时间为 baseDelay*2^,调用者可以执行 maxDelay 来限制最大延迟时间:

// 来源于:k8s.io/client-go/util/workqueue/default_rate_limiters.go
type ItemExponentialFailureRateLimiter struct {
	failuresLock sync.Mutex
	failures     map[interface{}]int

	baseDelay time.Duration
	maxDelay  time.Duration
}

函数 NewItemExponentialFailureRateLimiter() 和 DefaultItemBasedRateLimiter() 方法返回创建该类型的实例:

// 来源于:k8s.io/client-go/util/workqueue/default_rate_limiters.go
func NewItemExponentialFailureRateLimiter(baseDelay time.Duration, maxDelay time.Duration) RateLimiter {
	return &ItemExponentialFailureRateLimiter{
		failures:  map[interface{}]int{},
		baseDelay: baseDelay,
		maxDelay:  maxDelay,
	}
}

func DefaultItemBasedRateLimiter() RateLimiter {
	return NewItemExponentialFailureRateLimiter(time.Millisecond, 1000*time.Second)
}

创建 ItemExponentialFailureRateLimiter(参考上面的 DefaultControllerRateLimiter() 函数):

NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second),
}

其它方法:

  • 每次调用 When() 方法时,对应 item 失败次数加 1。
  • NumRequests() 方法返回对象失败的次数;
  • Forget()方法将对象从限速器移除,相当于将失败次数置 0,这样下次延迟从 baseDelay 开始。

实现 RateLimter 接口的 ItemFastSlowRateLimiter 类型

实现 RateLimter 接口的 MaxOfRateLimiter 类型

DelayingInterface 接口

RateLimitingInterface 接口

workqueue 的使用场景

// 来源于:https://github.com/kubernetes/sample-controller/blob/master/controller.go
type Controller struct {
    ...
	workqueue workqueue.RateLimitingInterface
    ...
}

func NewController(
	kubeclientset kubernetes.Interface,
	sampleclientset clientset.Interface,
	deploymentInformer appsinformers.DeploymentInformer,
	fooInformer informers.FooInformer) *Controller {
    ...
	controller := &Controller{
        ...
        workqueue:         workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Foos"),
        ...
    }
    ...
}

func (c *Controller) enqueueFoo(obj interface{}) {
	var key string
	var err error
	if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
		utilruntime.HandleError(err)
		return
	}
	c.workqueue.AddRateLimited(key)
}

func (c *Controller) processNextWorkItem() bool {
    ...
	err := func(obj interface{}) error {
		defer c.workqueue.Done(obj)
		var key string
		var ok bool
		if key, ok = obj.(string); !ok {
			c.workqueue.Forget(obj)
			utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
			return nil
		}
		if err := c.syncHandler(key); err != nil {
			c.workqueue.AddRateLimited(key)
			return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
		}
		c.workqueue.Forget(obj)
		klog.Infof("Successfully synced '%s'", key)
		return nil
	}(obj)
    ...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值