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 的功能确实是这样完成的。

相关推荐
飞酱不会电脑1 小时前
云计算第四阶段 CLOUD2周目 01-03
云原生·容器·kubernetes
程序那点事儿3 小时前
k8s 之安装busybox
云原生·容器·kubernetes
weixin_453965004 小时前
master节点k8s部署]33.ceph分布式存储(四)
分布式·ceph·kubernetes
是芽芽哩!4 小时前
【Kubernetes】常见面试题汇总(五十八)
云原生·容器·kubernetes
福大大架构师每日一题15 小时前
22.1 k8s不同role级别的服务发现
容器·kubernetes·服务发现
weixin_4539650016 小时前
[单master节点k8s部署]30.ceph分布式存储(一)
分布式·ceph·kubernetes
weixin_4539650016 小时前
[单master节点k8s部署]32.ceph分布式存储(三)
分布式·ceph·kubernetes
tangdou36909865516 小时前
1分钟搞懂K8S中的NodeSelector
云原生·容器·kubernetes
later_rql19 小时前
k8s-集群部署1
云原生·容器·kubernetes
weixin_4539650021 小时前
[单master节点k8s部署]31.ceph分布式存储(二)
分布式·ceph·kubernetes