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
        }
    
posted @ 2023-06-29 16:09  左扬  阅读(123)  评论(0编辑  收藏  举报
levels of contents