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主要由queue、dirty、processing组成。
- 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以延迟队列为基础,实现三种不同的限速算法:
- 令牌桶算法
- 排队指数算法
- 计数器算法
学习这些算法思想感觉对网关访问流量限制也有一定的帮助,例如SpringCloud Gateway结合Redis实现了令牌桶算法来实现流量限速。
参考资料
《Kubernetes源码剖析》阅读笔记一——client-go源码结构
《kubernetes源码剖析》阅读笔记二——informer机制