1. 前言
轉載請說明原文出處, 尊重他人勞動成果!
源碼位置: https://github.com/nicktming/client-go/tree/tming-v13.0/util/workqueue
分支: tming-v13.0 (基于v13.0版本)
本文將分析
util
包中的workqueue
. 在各類controller
中經常會使用該workqueue
中的一些類.
architecture.png
2. queue
2.1 Interface接口
type Interface interface {
Add(item interface{})
Len() int
Get() (item interface{}, shutdown bool)
Done(item interface{})
ShutDown()
ShuttingDown() bool
}
這里有幾點需要注意一下:
1. 增加了Done
方法, 告訴queue
對這個item
的處理已經結束了.
2. 以往的queue在pop之后就對這個item的狀態不管了, 但是在該iterface
中明顯需要進行管理, 因為需要實現Done
方法, 所以需要知道哪些item
正在處理.
2.2 實現類Type
type Type struct {
// queue定義了需要處理的item的順序
// 這些element應該出現在dirty中 而不會在processing中.
queue []t
// dirty保存著那些需要被處理的item
dirty set
// 代表那些正在被處理的元素,
processing set
cond *sync.Cond
shuttingDown bool
metrics queueMetrics
unfinishedWorkUpdatePeriod time.Duration
clock clock.Clock
}
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)
}
2.3 方法
Get
// 返回頭部item 和 queue是否關閉
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
}
// 獲得隊列的頭 并且更新queue數組
item, q.queue = q.queue[0], q.queue[1:]
q.metrics.get(item)
// 1. 將該item加入到processing中, 表明該item正在被處理
// 2. 將該item從dirty中刪除
q.processing.insert(item)
q.dirty.delete(item)
return item, false
}
- 如果
queue
沒有關閉 但是目前沒有元素 一直waiting
- 如果
queue
已經關閉 則返回nil
,true
- 獲得隊列的頭
item
并且更新queue
數組- 將該
item
加入到processing
中 表明該item
正在被處理- 從
dirty
和queue
中刪除該item
- 返回該
item
,false
Add 和 Done
func (q *Type) Add(item interface{}) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
// 如果queue已經關閉了
if q.shuttingDown {
return
}
// 如果已經在queue中了 就直接返回了
if q.dirty.has(item) {
return
}
q.metrics.add(item)
// 加入到dirty中
q.dirty.insert(item)
// 如果該元素正在被處理, 那直接返回了 不會加入到queue中
if q.processing.has(item) {
return
}
q.queue = append(q.queue, item)
q.cond.Signal()
}
// 表明該item已經結束了
func (q *Type) Done(item interface{}) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.metrics.done(item)
// 如果結束了 就從processing中刪除
q.processing.delete(item)
// 如果dirty中有 在把item加回去
// 這里主要是因為在處理item的過程中, 上流程序又調用了Add方法
// 因為該item正在被處理, 此時如果加入到queue中, 然后又Get拿到該item
// 此時就會有兩個同樣的item正在被處理
// 所以最終設計的目的是為了保證正在被處理的過程中保證每個都不一樣 不會出現有兩個相同的item正在被處理
if q.dirty.has(item) {
q.queue = append(q.queue, item)
q.cond.Signal()
}
}
該方法的實現是為了保證不能同時有兩個相同的
item
正在被處理
1. 如果一個
item
正在被處理, 此時又add
了一個相同的item
, 那此時不能加入到queue
中(把該item暫時加到了dirty
), 此時如果加入到queue
中, 然后又Get
拿到該item
, 那么同一時間就會有兩個相同的item
在被處理
2. 所以在該item
處理結束(Done
方法)的時候會檢查該item
是否有在被處理的過程中再次加入queue
, 也就是判斷dirty
中是否含有該item
, 如果有, 則加入到queue
中
3. delaying_queue
3.1 DelayingInterface接口
type DelayingInterface interface {
Interface
// 在此刻過duration時間后再加入到queue中
AddAfter(item interface{}, duration time.Duration)
}
DelayingInterface is an Interface that can Add an item at a later time. This makes it easier to requeue items after failures without ending up in a hot-loop.
該接口涉及的目的就是可以避免某些失敗的
items
陷入熱循環. 因為某些item
在出隊列進行處理有可能失敗, 失敗了用戶就有可能將該失敗的item
重新進隊列, 如果短時間內又出隊列有可能還是會失敗(因為短時間內整個環境可能沒有改變等等), 所以可能又重新進隊列等等, 因此陷入了一種hot-loop
.
所以就為用戶提供了
AddAfter
方法, 可以允許用戶告訴DelayingInterface
過多長時間把該item
加入隊列中.
3.2 實現類delayingType
因為有延遲的時間, 所以那些
item
到時間了可以進入隊列了, 那些沒到時間還不能進入隊列, 所以waitFor
和waitForPriorityQueue
應運而生.
waitFor: 保存了包括數據
data
和該item
什么時間起(readyAt
)就可以進入隊列了.
waitForPriorityQueue: 是用于保存waitFor
的優先隊列, 按readyAt
時間從早到晚排序. 先ready
的item
先出隊列.
// delayingType wraps an Interface and provi`des delayed re-enquing
// 提供有延遲的重進隊列
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 retryMetrics
}
// waitFor holds the data to add and the time it should be added
type waitFor struct {
data t
// 該data可以加入queue的時間
readyAt time.Time
// index in the priority queue (heap)
index int
}
type waitForPriorityQueue []*waitFor
3.3 方法
newDelayingQueue
func newDelayingQueue(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
}
這里需要注意的是
1. 啟動了goroutine執行waitingLoop方法, 關于waitingLoop
在后面介紹.
2. NewNamed(name)是一個Type對象實例(2.2中的Type)
AddAfter
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
// 如果不需要延遲 直接調用iterface(Type)中的Add方法
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)}:
// 構造一個waitFor放到waitingForAddCh(一個channel, 緩沖1000)
}
}
1. 構造一個
waitFor
放到channel(waitingForAddCh)
中,waitingLoop
會在另外一端接收.
waitingLoop
func (q *delayingType) waitingLoop() {
defer utilruntime.HandleCrash()
// Make a placeholder channel to use when there are no items in our list
never := make(<-chan time.Time)
waitingForQueue := &waitForPriorityQueue{}
heap.Init(waitingForQueue)
waitingEntryByData := map[t]*waitFor{}
for {
// queue關閉返回
if q.Interface.ShuttingDown() {
return
}
now := q.clock.Now()
// 將那些已經ready好了的item可以加入到queue中
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)
}
// nextReadyAt啟一個channel用于存著下次可以加入queue的時間
nextReadyAt := never
if waitingForQueue.Len() > 0 {
// 優先隊列中的第一個肯定是最早ready的
entry := waitingForQueue.Peek().(*waitFor)
nextReadyAt = q.clock.After(entry.readyAt.Sub(now))
}
select {
case <-q.stopCh:
return
// ticker的操作 每隔10s會調用
// 有可能去增加已經ready items
case <-q.heartbeat.C():
// continue the loop, which will add ready items
// 下次ready的時間到了
case <-nextReadyAt:
// continue the loop, which will add ready items
case waitEntry := <-q.waitingForAddCh:
// 從AddAfter過來的數據
if waitEntry.readyAt.After(q.clock.Now()) {
// 如果時間沒到 就加入waitingForQueue中
// waitingEntryByData用于保存waitEntry.data與waitEntry的關系
insert(waitingForQueue, waitingEntryByData, waitEntry)
} else {
// 直接加入到queue中
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
}
}
}
}
}
func insert(q *waitForPriorityQueue, knownEntries map[t]*waitFor, entry *waitFor) {
// if the entry already exists, update the time only if it would cause the item to be queued sooner
// 如果已經在waitingQueue中了 取readyAt最小的那個 為了可以讓它早點出queue
// 就是說增加的item重復了 ready的時間取最早的一個
existing, exists := knownEntries[entry.data]
if exists {
if existing.readyAt.After(entry.readyAt) {
existing.readyAt = entry.readyAt
heap.Fix(q, existing.index)
}
return
}
heap.Push(q, entry)
knownEntries[entry.data] = entry
}
這里啟動一個
for
循環:
1. 只有隊列關閉的時候才會退出.
2. 利用channel(q.waitingForAddCh)
一直接受從AddAfter
過來的waitFor
.
3. 一直將已經ready
的item
加入到隊列中.
注意: 如果有兩個相同的
data
加入時,waitFor
中取ready
時間最早的那一個.
4. default_rate_limit
4.1 RateLimiter接口
type RateLimiter interface {
// 返回該item應該要等待多長時間
When(item interface{}) time.Duration
// 停止跟蹤該item
Forget(item interface{})
// 返回該item失敗的次數
NumRequeues(item interface{}) int
}
一個控制速率的接口. 在
3.1 DelayingInterface接口
中提到hot-loop
, 那關于等待多長時間可以根據失敗的次數有關, 比如最簡單的失敗一次等待時間就增加一倍, 第一次失敗等待1s
, 第二次失敗等待2s
, 第三次失敗等待4s
, 以此類推. 那RateLimiter
接口就是這樣一個控制速率的抽象定義, 接下來看一個它的實現類.
4.2 ItemExponentialFailureRateLimiter
type ItemExponentialFailureRateLimiter struct {
failuresLock sync.Mutex
// 存的失敗的item
failures map[interface{}]int
baseDelay time.Duration
maxDelay time.Duration
}
var _ RateLimiter = &ItemExponentialFailureRateLimiter{}
func NewItemExponentialFailureRateLimiter(baseDelay time.Duration, maxDelay time.Duration) RateLimiter {
return &ItemExponentialFailureRateLimiter{
failures: map[interface{}]int{},
baseDelay: baseDelay,
maxDelay: maxDelay,
}
}
func DefaultItemBasedRateLimiter() RateLimiter {
return NewItemExponentialFailureRateLimiter(time.Millisecond, 1000*time.Second)
}
4.3 方法
func (r *ItemExponentialFailureRateLimiter) When(item interface{}) time.Duration {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
// 該item失敗的次數
exp := r.failures[item]
r.failures[item] = r.failures[item] + 1
// The backoff is capped such that 'calculated' value never overflows.
backoff := float64(r.baseDelay.Nanoseconds()) * math.Pow(2, float64(exp))
if backoff > math.MaxInt64 {
return r.maxDelay
}
calculated := time.Duration(backoff)
if calculated > r.maxDelay {
return r.maxDelay
}
return calculated
}
func (r *ItemExponentialFailureRateLimiter) NumRequeues(item interface{}) int {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
return r.failures[item]
}
func (r *ItemExponentialFailureRateLimiter) Forget(item interface{}) {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
delete(r.failures, item)
}
直接通過一個例子更好說明:
example.png
5. rate_limiting_queue
5.1 RateLimitingInterface接口
type RateLimitingInterface interface {
DelayingInterface
AddRateLimited(item interface{})
Forget(item interface{})
NumRequeues(item interface{}) int
}
因為
DelayingInterface
已經有了AddAfter
方法, 但是到底是after
多長時間并沒有進行控制, 最簡單的就是一個常量時間, 對所有的item
都一樣. 但是這樣的話也有可能會造成一些問題, 有些item
因為失敗來回來回進入隊列十幾次, 而有些item
才失敗一次, 就有可能排在這個失敗十幾次的item
后面, 如果還是不成功, 這樣就會造成資源的浪費.
所以
RateLimitingInterface
就在DelayingInterface
加入對item
的速率進行控制, 并且與失敗的次數進行相關.
5.2 實現類以及方法
很常規.
type rateLimitingType struct {
DelayingInterface
rateLimiter RateLimiter
}
func (q *rateLimitingType) AddRateLimited(item interface{}) {
q.DelayingInterface.AddAfter(item, q.rateLimiter.When(item))
}
func (q *rateLimitingType) NumRequeues(item interface{}) int {
return q.rateLimiter.NumRequeues(item)
}
// 刪除該item
func (q *rateLimitingType) Forget(item interface{}) {
q.rateLimiter.Forget(item)
}