5. client-go中普通Queue的源码解析

本文是胡涛大佬所出版的《Kubernetes Operator 进阶开发》,强烈推荐各位阅读原书,本文仅仅留作个人心得,如有侵权立马删除。


client-go 中有三种队列,在文件夹 client-go/util/workqueue/

  1. Queue:普通队列,对应文件:queue.go
  2. DelayingQueue:延迟队列,对应文件:delaying_queue. go
  3. RateLimitingQueue:限速队列,对应文件:rate_limiting_queue. go

其中最基本的 QueueInterface 定义如下:

go 复制代码
type Interface interface {
	Add(item interface{})
	Len() int
	Get() (item interface{}, shutdown bool)
	Done(item interface{})
	ShutDown()
	ShutDownWithDrain()
	ShuttingDown() bool
}

然后具体的实现的结构体是:

go 复制代码
type Type struct {
	queue []t
	dirty set
	processing set

	cond *sync.Cond

	shuttingDown bool
	drain        bool

	metrics queueMetrics

	unfinishedWorkUpdatePeriod time.Duration
	clock                      clock.WithTicker
}

其中存放元素的地方有三个:

  1. queue []t:一个切片,利用其有序的特质,来存放元素的处理顺序
  2. dirty:一个 set 类型,其中存储所有需要处理的元素,其中的元素也会保存在 queue 里面,但是集合中的元素是无序的且是唯一的
  3. processing :一个 set 类型,其中存储正在被处理的元素,来自于 queue 中的 pop,而且也会被从 dirty 中删除

实际上 set 就是一个 map,下面是 set 的相关的定义:

go 复制代码
type empty struct{}
type t interface{}
type set map[t]empty

func (s set) has(item t) bool {
	_, exists := s[item]
	return exists
}

func (s set) insert(item t) {
	s[item] = empty{}
}

func (s set) delete(item t) {
	delete(s, item)
}

func (s set) len() int {
	return len(s)
}

然后我们可以仔细看看 Add()Get(),以及 Done() 的代码:

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()
}

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[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:]

	q.metrics.get(item)

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

	return item, false
}

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()
	} else if q.processing.len() == 0 {
		q.cond.Signal()
	}
}

关于这几段代码的观察之后,我们基本可以进一步明白 dirtyqueue 以及 processing 的关系了:

  1. queue:表示等待被处理的项,即按照添加顺序排列的待处理项的队列
  2. processing:表示当前正在被处理的项,即已经从队列中取出但尚未完成处理的项集合
  3. dirty:表示需要重新处理的项,即在处理过程中发现需要重新处理的项的集合。这些项可能是在处理中发生了错误,或者在处理期间又收到了相同项的请求,因此需要重新放回队列中以进行处理,这也是叫做 dirty 的原因

所以正常情况下,Queue 的使用应该是:

go 复制代码
queue := NewQueue() // 创建一个新的工作队列
item := "item1"
go func() {
    for {
        item, shutdown := queue.Get()
        if shutdown {
            // 如果 shutdown=true,则需要结束处理
            break
        }
        // 处理 item
        // ...
        queue.Done(item) // 处理完成后调用 Done() 方法
    }
}()

// 另外的goroutine做的事情
queue.Add(item) // 向队列中添加一个item

不过因为我对 go 编程不太了解,所以为了证实我对 dirty 这个字段的猜想,因此首先在 queue.go 里面编写了一段获取 dirty 长度的代码:

go 复制代码
func (q *Type) GetDirtyLen() int {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	return q.dirty.len()
}

然后编写了一个 test 函数:

go 复制代码
func TestDirty(t *testing.T) {
	queue := workqueue.New()

	// 测试 dirty 的功能
	// 首先是 producer
	const producers = 3
	producerWG := sync.WaitGroup{}
	producerWG.Add(producers)
	for i := 0; i < producers; i++ {
		go func(i int) {
			defer producerWG.Done()

			for j := 0; j < 3; j++ {
				queue.Add("the same item")
				time.Sleep(time.Millisecond)
				t.Logf("the length of queue is %v, the len of dirty is %v", queue.Len(), queue.GetDirtyLen())
			}
		}(i)
	}
	producerWG.Wait()

	// 然后是 consumer
	const consumers = 1
	consumerWG := sync.WaitGroup{}
	consumerWG.Add(consumers)
	for i := 0; i < consumers; i++ {
		go func(i int) {
			defer consumerWG.Done()
			for {
				item, quit := queue.Get()
				if quit {
					return
				}
				t.Logf("Worker %v: begin processing %v", i, item)
				time.Sleep(3 * time.Second)
				t.Logf("Worker %v: done processing %v", i, item)
				queue.Done(item)
			}
		}(i)
	}
	for i := 0; i < 30; i++ {
		t.Logf("the length of queue is %v, the len of dirty is %v", queue.Len(), queue.GetDirtyLen())
		time.Sleep(1 * time.Second)
	}
	// 优雅关闭
	workqueue.Interface.ShutDown(queue)

}

从结果来看:

shell 复制代码
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:109: the length of queue is 1, the len of dirty is 1
    queue_test.go:135: the length of queue is 1, the len of dirty is 1
    queue_test.go:127: Worker 0: begin processing the same item
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:129: Worker 0: done processing the same item
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0
    queue_test.go:135: the length of queue is 0, the len of dirty is 0

从结果来看 dirty 的功能确实是这样完成的。

相关推荐
Hfc.3 小时前
ubuntu20.04系统搭建k8s1.28集群-docker作为容器运行时
ubuntu·kubernetes
alden_ygq10 小时前
Kubernetes Horizontal Pod Autosscaler(HPA)核心机制解析
云原生·容器·kubernetes
格桑阿sir10 小时前
Kubernetes控制平面组件:Kubelet详解(三):CRI 容器运行时接口层
docker·kubernetes·containerd·kubelet·cri-o·容器运行时·cri
hwj运维之路21 小时前
k8s监控方案实践(三):部署与配置Grafana可视化平台
云原生·kubernetes·grafana
和计算机搏斗的每一天21 小时前
k8s之探针
云原生·容器·kubernetes
项目題供诗1 天前
黑马k8s(四)
云原生·容器·kubernetes
杰克逊的日记1 天前
大项目k8s集群有多大规模,多少节点,有多少pod
云原生·容器·kubernetes
小张童鞋。1 天前
k8s之k8s集群部署
云原生·容器·kubernetes
long_21451 天前
k8s中ingress-nginx介绍
kubernetes·ingress-nginx
luck_me51 天前
k8s v1.26 实战csi-nfs 部署
linux·docker·云原生·容器·kubernetes