《kubernetes源码剖析》阅读笔记三——WorkQueue

1,874 阅读3分钟

1.阅读目标

  • WorkQueue

2.WorkQueue

接口组合关系:

2.1 FIFO队列

接口定义如下:

type Interface interface {
	Add(item interface{})
	Len() int
	Get() (item interface{}, shutdown bool)
	Done(item interface{})
	ShutDown()
	ShuttingDown() bool
}

接口实现:

type Type struct {
	// queue defines the order in which we will work on items. Every
	// element of queue should be in the dirty set and not in the
	// processing set.
	queue []t

	// dirty defines all of the items that need to be processed.
	dirty set

	// Things that are currently being processed are in the processing set.
	// These things may be simultaneously in the dirty set. When we finish
	// processing something and remove it from this set, we'll check if
	// it's in the dirty set, and if so, add it to the queue.
	processing set

	cond *sync.Cond

	shuttingDown bool

	metrics queueMetrics

	unfinishedWorkUpdatePeriod time.Duration
   
	clock                      clock.Clock
}

该FIFO主要由queuedirtyprocessing组成。

  • queue: 真正存储元素的地方,保证元素顺序。
  • dirty: 使用map实现的set,有去掉重复元素的功能,并保证同一元素只会被处理一次。
  • processing: 标记正在被处理的元素。

具体可查看FIFO的Add实现方法:

// Add marks item as needing processing.
func (q *Type) Add(item interface{}) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	if q.shuttingDown {
		return
	}
    //检查item在dirty set中是否已经存在
	if q.dirty.has(item) {
		return
	}

	q.metrics.add(item)
	
    //将item添加到dirty set
	q.dirty.insert(item)
    //检查该item是否正在被处理
	if q.processing.has(item) {
		return
	}
	
    //如果该item没有被处理则加入queue
	q.queue = append(q.queue, item)
	q.cond.Signal()
}

Get方法从队列中获取item

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 = q.queue[0], q.queue[1:]

	q.metrics.get(item)

	//标记该item正在被处理
	q.processing.insert(item)
    //将该item从dirty set中移除
	q.dirty.delete(item)

	return item, false
}

流程如下:

2.2 延迟队列

接口定义:

type DelayingInterface interface {
	Interface
	// AddAfter adds an item to the workqueue after the indicated duration has passed
	AddAfter(item interface{}, duration time.Duration)
}

实现类型数据结构:

type delayingType struct {
	Interface

	// clock tracks time for delayed firing
	clock clock.Clock

	// stopCh lets us signal a shutdown to the waiting loop
	stopCh chan struct{}
	// stopOnce guarantees we only signal shutdown a single time
	stopOnce sync.Once

	// heartbeat ensures we wait no more than maxWait before firing
	heartbeat clock.Ticker

	// waitingForAddCh is a buffered channel that feeds waitingForAdd
	waitingForAddCh chan *waitFor

	// metrics counts the number of retries
	metrics retryMetrics
}

队列初始化方法如下:


func NewDelayingQueueWithCustomClock(clock clock.Clock, name string) DelayingInterface {
	ret := &delayingType{
		Interface:       NewNamed(name),
		clock:           clock,
		heartbeat:       clock.NewTicker(maxWait),
		stopCh:          make(chan struct{}),
		waitingForAddCh: make(chan *waitFor, 1000),
		metrics:         newRetryMetrics(name),
	}

	go ret.waitingLoop()

	return ret
}

该方法初始化了waitingForAddCh(大小为1000的缓冲通道)channel,并在goroutine中执行了waitingLoop()。可以结合AddAfter方法来理解waitingForAddCh的作用。

func (q *delayingType) AddAfter(item interface{}, duration time.Duration) {
	// don't add if we're already shutting down
	if q.ShuttingDown() {
		return
	}

	q.metrics.retry()

	// immediately add things with no delay
	if duration <= 0 {
		q.Add(item)
		return
	}

	select {
	case <-q.stopCh:
		// unblock if ShutDown() is called
	case q.waitingForAddCh <- &waitFor{data: item, readyAt: q.clock.Now().Add(duration)}:
	}
}

如果延时时间未小于等于0则将item放入waitingForAddCh。在waitingLoop()方法中从waitingForAddCh获取waitEntry代码片段如下:

func (q *delayingType) waitingLoop() {
	
    ……

	for {
		if q.Interface.ShuttingDown() {
			return
		}

		now := q.clock.Now()

		// Add ready entries
		for waitingForQueue.Len() > 0 {
			entry := waitingForQueue.Peek().(*waitFor)
			if entry.readyAt.After(now) {
				break
			}

			entry = heap.Pop(waitingForQueue).(*waitFor)
			q.Add(entry.data)
			delete(waitingEntryByData, entry.data)
		}

		// Set up a wait for the first item's readyAt (if one exists)
		nextReadyAt := never
		if waitingForQueue.Len() > 0 {
			if nextReadyAtTimer != nil {
				nextReadyAtTimer.Stop()
			}
			entry := waitingForQueue.Peek().(*waitFor)
			nextReadyAtTimer = q.clock.NewTimer(entry.readyAt.Sub(now))
			nextReadyAt = nextReadyAtTimer.C()
		}

		select {
		case <-q.stopCh:
			return

		case <-q.heartbeat.C():
			// continue the loop, which will add ready items

		case <-nextReadyAt:
			// continue the loop, which will add ready items

		case waitEntry := <-q.waitingForAddCh:
			if waitEntry.readyAt.After(q.clock.Now()) {
				insert(waitingForQueue, waitingEntryByData, waitEntry)
			} else {
				q.Add(waitEntry.data)
			}

			drained := false
			for !drained {
				select {
				case waitEntry := <-q.waitingForAddCh:
					if waitEntry.readyAt.After(q.clock.Now()) {
						insert(waitingForQueue, waitingEntryByData, waitEntry)
					} else {
						q.Add(waitEntry.data)
					}
				default:
					drained = true
				}
			}
		}
	}
}

流程如下:

2.3 限速队列

RateLimitingQueue以延迟队列为基础,实现三种不同的限速算法:

  1. 令牌桶算法
  2. 排队指数算法
  3. 计数器算法

学习这些算法思想感觉对网关访问流量限制也有一定的帮助,例如SpringCloud Gateway结合Redis实现了令牌桶算法来实现流量限速。

参考资料

《Kubernetes源码剖析》阅读笔记一——client-go源码结构

《kubernetes源码剖析》阅读笔记二——informer机制

《kubernetes源码剖析》阅读笔记三——WorkQueue

《kubernetes源码剖析》阅读笔记四——一阶段总结