Kubernetes编程——client-go基础—— 工作队列(workqueue)
工作队列(workqueue[wɜːk][kjuː])
https://github.com/kubernetes/kubernetes/tree/release-1.27/staging/src/k8s.io/client-go/util/workqueue
我理解意思是说: 这里说的 "工作队列" 指的一个数据结构。用户可以按照队列所预定义的顺序向这个队列中添加和取出元素。这种队列是一种优先队列。 client-go 在 https://github.com/kubernetes/kubernetes/tree/release-1.27/staging/src/k8s.io/client-go/util/workqueue 中提供了一种强大的优先队列,可以让实现控制器变得更加方便。
// https://github.com/kubernetes/kubernetes/blob/release-1.27/staging/src/k8s.io/client-go/util/workqueue/queue.go
type Interface interface {
Add(item interface{}) // 将该元素加入队列,并调用Done(item)方法。
Len() int // 返回队列长度
Get() (item interface{}, shutdown bool) // 调用方可以通过检查shutdown的值来确定是否需要结束对该队列的处理。如果shutdown为true,调用方应该停止调用Get()方法以及其他可能会访问队列的方法,以确保正确关闭队列和释放相关资源。
Done(item interface{}) // 调用完Done(item)方法后,该元素才会被加入队列。
ShutDown()
ShutDownWithDrain() // 优雅地关闭一个队列并等待队列中的项目完成处理。
ShuttingDown() bool
}
Add() 方法的源代码位置: https://github.com/kubernetes/kubernetes/blob/release-1.27/staging/src/k8s.io/client-go/util/workqueue/queue.go#L27, 这段代码定义了一个名为 Add() 的方法,用于将 item 添加队列。具体说明如下:
// Add marks item as needing processing.
func (q *Type) Add(item interface{}) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
// 如果队列正在关闭,这行代码会直接返回,不会对 item 进行处理。
if q.shuttingDown {
return
}
// 如果队列已经在 dirty 中,这行代码将直接返回,不会对 item 进行处理。
if q.dirty.has(item) {
return
}
// 将 item 添加到 metrics 中,用于记录已添加的 item 总数。
q.metrics.add(item)
// 将 item 插入到 dirty
// dirty defines all of the items that need to be processed.
q.dirty.insert(item)
// 检查 item 是否在 processing,如果是,则直接返回,不会将项目添加到队列。
if q.processing.has(item) {
return
}
// 将 item 添加到队列的末尾。
q.queue = append(q.queue, item)
// 会发送一个信号,通知其他可能正在等待添加新 item 的 goroutine。
q.cond.Signal()
}
Done() 方法的源代码位置: https://github.com/kubernetes/kubernetes/blob/release-1.27/staging/src/k8s.io/client-go/util/workqueue/queue.go#L223 , Done() 的方法,它表示一个项目已完成处理,如果在处理过程中该项目再次被标记为需要处理(dirty),则会将其重新添加到队列中以进行再处理。具体说明如下:
// Done marks item as done processing, and if it has been marked as dirty again
// while it was being processed, it will be re-added to the queue for
// re-processing.
func (q *Type) Done(item interface{}) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
// 调用了 q.metrics.done() 方法以更新有关已完成 item 的 metrics。
q.metrics.done(item)
// 从处理中删除已完成处理的item。
q.processing.delete(item)
// 检查已完成处理的 item 是否仍然在 dirty 中。
// 如果是,则将该项目重新添加到队列的尾部,并向条件变量发送信号,告诉它有新 item 可以处理。
// dirty defines all of the items that need to be processed.
if q.dirty.has(item) {
q.queue = append(q.queue, item)
q.cond.Signal()
// 如果 q.processing.len() == 0,则通知在等待所有 item 完成处理的方法,现在可以继续执行了。
} else if q.processing.len() == 0 {
q.cond.Signal()
}
}
Get() 方法的源代码位置: https://github.com/kubernetes/kubernetes/blob/release-1.27/staging/src/k8s.io/client-go/util/workqueue/queue.go#L196, Get() 的方法,它会阻塞当前协程直到可以返回一个要处理的 item。如果返回的 shutdown 为 true,调用者应该结束其协程。处理完成后,您必须使用 Done 方法并传入 item 参数。具体说明如下:
// Get blocks until it can return an item to be processed. If shutdown = true,
// the caller should end their goroutine. You must call Done with item when you
// have finished processing it.
func (q *Type) Get() (item interface{}, shutdown bool) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
// 这个循环会在队列为空并且没有关闭时使当前协程进入等待状态。q.cond.Wait() 会释放互斥锁并等待条件变量发送信号。
for len(q.queue) == 0 && !q.shuttingDown {
q.cond.Wait()
}
// 如果队列为空,则表示我们必须关闭,返回 nil 和 true。
if len(q.queue) == 0 {
// We must be shutting down.
return nil, true
}
// 从队列中取出第一个 item,并将队列的第一个元素设置为空,以便垃圾回收。然后更新队列,排除已取出的项目。
item = q.queue[0]
// The underlying array still exists and reference this object, so the object will not be garbage collected.
q.queue[0] = nil
q.queue = q.queue[1:]
// 更新 metrics,表示获取 item 。
q.metrics.get(item)
// 将 item 插入到 processing(这是一个 set{} 数据类型)
q.processing.insert(item)
q.dirty.delete(item)
return item, false
}

浙公网安备 33010602011771号