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 }