本文是胡涛大佬所出版的《Kubernetes Operator 进阶开发》,强烈推荐各位阅读原书,本文仅仅留作个人心得,如有侵权立马删除。
在 client-go
中有三种队列,在文件夹 client-go/util/workqueue/
:
Queue
:普通队列,对应文件:queue.go
DelayingQueue
:延迟队列,对应文件:delaying_queue. go
RateLimitingQueue
:限速队列,对应文件:rate_limiting_queue. go
其中最基本的 Queue
的 Interface
定义如下:
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
}
其中存放元素的地方有三个:
queue []t
:一个切片,利用其有序的特质,来存放元素的处理顺序dirty
:一个set
类型,其中存储所有需要处理的元素,其中的元素也会保存在queue
里面,但是集合中的元素是无序的且是唯一的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()
}
}
关于这几段代码的观察之后,我们基本可以进一步明白 dirty
,queue
以及 processing
的关系了:
queue
:表示等待被处理的项,即按照添加顺序排列的待处理项的队列processing
:表示当前正在被处理的项,即已经从队列中取出但尚未完成处理的项集合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
的功能确实是这样完成的。