目录
目录
ItemExponentialFailureRateLimiter
为什么介绍队列
如下图示:
可以看出workqueue在client-go及kubernetes控制器中的重要作用。
client-go中的workqueue,类似于golang语言中的chan,主要用于并发程序直接的数据同步。上述kubernetes的控制器模型 通过client-go的informer watch资源变化,当资源发生变化时会通过回调函数将资源写入队列,由controller中的worker消费者完成业务处理。 版本是:client-go v11.0.0
通用队列
代码位置:/k8s.io/client-go/util/workqueue/queue.go
通过接口定义
// interface类型,实现方式
type Interface interface {
Add(item interface{}) // 向队列中添加一个元素,interface{}类型
Len() int // 队列长度
Get() (item interface{}, shutdown bool) // 队列元素 队列是否关闭 chan的<-操作类似 第二个返回值告知队列是否已经关闭了
Done(item interface{}) // 告知队列该元素已经处理完了
ShutDown() // 关闭队列
ShuttingDown() bool // 获取队列关闭状态
}
Get操作不仅仅是获取队列中的元素,当处理完的时候通过Done函数将元素从队列中完全去除。chan读取操作会将元素从chan对象中去除。
类型定义
// Type is a work queue (see the package comment).
type Type struct {
// 队列定义处理元素的顺序。队列中的元素在 dirty set 保留一份
// processing set 处理map对象
queue []t // 队列 元素slice
// dirty 存储的是所有要被处理的元素map结构
dirty set
// processing set 存储的是目前正砸被处理的元素。有可能重复的元素 同时存储在dirty set。
// 当处理完元素,并从processing set移除时,检查是否存在dirty set,存在的话添加到queue 数组中
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 // map类型
// map增删查操作
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)
}
// 其他文件 时间相关的函数 定时器、打点器
type Clock interface {
Now() time.Time
Since(time.Time) time.Duration
After(time.Duration) <-chan time.Time
NewTimer(time.Duration) Timer
Sleep(time.Duration)
NewTicker(time.Duration) Ticker
}
初始化
func newQueue(c clock.Clock, metrics queueMetrics, updatePeriod time.Duration) *Type {
t := &Type{
clock: c,
dirty: set{},
processing: set{},
cond: sync.NewCond(&sync.Mutex{}),
metrics: metrics,
unfinishedWorkUpdatePeriod: updatePeriod,
}
go t.updateUnfinishedWorkLoop() // 队列初始化的时候 创建协程,主要是干啥的?分析下
return t
}
// 队列没有关闭的时候 定时 同步metrics信息
func (q *Type) updateUnfinishedWorkLoop() {
// 创建 unfinishedWorkUpdatePeriod 打点器
t := q.clock.NewTicker(q.unfinishedWorkUpdatePeriod)
defer t.Stop()
for range t.C() {
if !func() bool {
q.cond.L.Lock()
defer q.cond.L.Unlock()
if !q.shuttingDown {
q.metrics.updateUnfinishedWork()
return true
}
return false
}() {
return
}
}
}
队列基本方法
// 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
}
// 即将处理的 dirty set 中存在,避免重复添加
if q.dirty.has(item) {
return
}
q.metrics.add(item)
// 添加到 即将处理的 dirty set
q.dirty.insert(item)
// 正在处理 直接返回
// 应对的场景:重复添加元素的场景,get表示 处理中 done表示处理完成
if q.processing.has(item) {
return
}
// 均不存在 添加到queue中
q.queue = append(q.queue, item)
q.cond.Signal()
}
queue和dirty set各自维护队列中的相同元素。为什么要存在这个dirty set呢?
- set类型肯定最快,数组需要遍历效率太低,查找。
- 重复添加的场景
// Get函数阻塞直到队列中添加元素。队列关闭,调用方结束goroutine。
// 处理完队列中的元素,应该调用 Done 函数 从 正在处理的set 中释放。
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 和 dirty set 是联动的
item, q.queue = q.queue[0], q.queue[1:]
q.metrics.get(item)
q.processing.insert(item)
q.dirty.delete(item)
return item, false
}
// 处理完元素后,从正在处理的set 中释放。 重复添加的场景,添加到 queue 中,并发信号进行处理
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()
}
}
其他函数都比较简单,获取队列长度、关闭队列、获取队列的关闭状态等。
简单的DEMO程序示例
func TestBasic(t *testing.T) {
// If something is seriously wrong this test will never complete.
q := workqueue.New()
// Start producers
const producers = 50
producerWG := sync.WaitGroup{}
producerWG.Add(producers)
// 生产者 50 个
// 每个生产者都会重复添加元素
for i := 0; i < producers; i++ {
go func(i int) {
defer producerWG.Done()
for j := 0; j < 50; j++ {
q.Add(i)
time.Sleep(time.Millisecond)
}
}(i)
}
fmt.Println(q.Len())
// Start consumers
const consumers = 10
consumerWG := sync.WaitGroup{}
consumerWG.Add(consumers)
// 10个消费者,消费50个数据 一直消费到队列关闭
for i := 0; i < consumers; i++ {
go func(i int) {
defer consumerWG.Done()
// Every worker will re-add every item up to two times.
// This tests the dirty-while-processing case.
counters := map[interface{}]int{}
for {
item, quit := q.Get()
if item == "added after shutdown!" {
t.Errorf("Got an item added after shutdown.")
}
if quit {
return
}
/*
counters[item]++
if counters[item] < 2 {
q.Add(item)
}
*/
t.Logf("Worker %v: begin processing %v", i, item)
time.Sleep(3 * time.Millisecond)
t.Logf("Worker %v: done processing %v", i, item)
q.Done(item)
}
}(i)
}
producerWG.Wait()
q.ShutDown()
q.Add("added after shutdown!")
consumerWG.Wait()
}
func TestReinsert(t *testing.T) {
q := workqueue.New()
q.Add("foo")
// Start processing 开始处理场景
i, _ := q.Get()
if i != "foo" {
t.Errorf("Expected %v, got %v", "foo", i)
}
// Add it back while processing 再次重复添加,只会添加到 dirty set 中
q.Add(i)
// Finish it up 处理完成
q.Done(i)
// It should be back on the queue
i, _ = q.Get()
if i != "foo" {
t.Errorf("Expected %v, got %v", "foo", i)
}
// Finish that one up
q.Done(i)
if a := q.Len(); a != 0 {
t.Errorf("Expected queue to be empty. Has %v items", a)
}
}
分析到这里,通用队列就分析完了。
延时队列
类型定义
// 延迟向队列中添加元素。 避免失败之后形成热循环
type DelayingInterface interface {
Interface // 通用队列接口
AddAfter(item interface{}, duration time.Duration) // 元素延迟添加接口
}
// delayingType wraps an Interface and provides delayed re-enquing
type delayingType struct {
Interface
clock clock.Clock // 时钟,获取时间
stopCh chan struct{} // 退出信号 等待循环协程
heartbeat clock.Ticker // 打点器,心跳唤醒协程
waitingForAddCh chan *waitFor // 缓冲 等待添加的元素 feeds waitingForAdd
// metrics counts the number of retries
metrics retryMetrics
deprecatedMetrics retryMetrics
}
// waitFor holds the data to add and the time it should be added
type waitFor struct {
data t // 元素
readyAt time.Time // 添加元素到队列的时间
// index in the priority queue (heap)
index int // 索引
}
// waitForPriorityQueue 根据 waitFor 元素实现优先级队列。添加的元素封装到waitFor 结构中。
// waitForPriorityQueue就把需要延迟的元素形成了一个队列,队列按照元素的延时添加的时间(readyAt)从小到大排序
// waitForPriorityQueue这个数组是有序的,排序方式是按照时间从小到大
// 实现了go/src/container/heap/heap.go中的Interface类型
type waitForPriorityQueue []*waitFor
// 队列长度
func (pq waitForPriorityQueue) Len() int {
return len(pq)
}
// 第i个元素小于 第j个元素,按照时间进行排序
func (pq waitForPriorityQueue) Less(i, j int) bool {
return pq[i].readyAt.Before(pq[j].readyAt)
}
// 元素交换,并记录索引
func (pq waitForPriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
pq[i].index = i
pq[j].index = j
}
// Push adds an item to the queue. Push should not be called directly; instead,
// use `heap.Push`.
func (pq *waitForPriorityQueue) Push(x interface{}) {
n := len(*pq)
item := x.(*waitFor)
item.index = n
*pq = append(*pq, item)
}
// Pop removes an item from the queue. Pop should not be called directly;
// instead, use `heap.Pop`.
func (pq *waitForPriorityQueue) Pop() interface{} {
n := len(*pq)
item := (*pq)[n-1]
item.index = -1
*pq = (*pq)[0:(n - 1)]
return item
}
// Peek returns the item at the beginning of the queue, without removing the
// item or otherwise mutating the queue. It is safe to call directly.
func (pq waitForPriorityQueue) Peek() interface{} {
return pq[0]
}
container/heap包可以用来构造优先级队列。后续分析heap包。
初始化
func newDelayingQueue(clock clock.Clock, name string) DelayingInterface {
ret := &delayingType{
Interface: NewNamed(name), // 通用队列
clock: clock, // 时间
heartbeat: clock.NewTicker(maxWait), // 10s 打点器
stopCh: make(chan struct{}), // 退出信号
waitingForAddCh: make(chan *waitFor, 1000), // buffer chan
metrics: newRetryMetrics(name),
deprecatedMetrics: newDeprecatedRetryMetrics(name),
}
//waitingLoop runs until the workqueue is shutdown and keeps a check on the list of items to be added.
go ret.waitingLoop()
return ret
}
延迟队列方法
// AddAfter adds the given item to the work queue after the given delay
func (q *delayingType) AddAfter(item interface{}, duration time.Duration) {
// 队列关闭直接退出
if q.ShuttingDown() {
return
}
q.metrics.retry()
q.deprecatedMetrics.retry()
// 不需要延迟添加
if duration <= 0 {
q.Add(item)
return
}
// 把元素封装成waitFor传入chan。
// 切记select没有default,所以可能会被阻塞。通过stopCh保证退出
select {
case <-q.stopCh:
// unblock if ShutDown() is called
case q.waitingForAddCh <- &waitFor{data: item, readyAt: q.clock.Now().Add(duration)}:
}
}
// waitingLoop runs until the workqueue is shutdown and keeps a check on the list of items to be added.
// 延迟队列核心代码,检查chan中元素
func (q *delayingType) waitingLoop() {
defer utilruntime.HandleCrash()
// 当没有元素需要延时添加的时候利用这个变量实现长时间等待
never := make(<-chan time.Time)
// 优先级队列并初始化
waitingForQueue := &waitForPriorityQueue{}
heap.Init(waitingForQueue)
// map 避免重复添加,重复添加只更新时间
waitingEntryByData := map[t]*waitFor{}
// 开始循环
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
}
// 时间到,从优先级队列拿出方法哦通用队列中 按时间顺序从小到大
// 1.heap.Pop()弹出的是第一个元素,waitingForQueue.Pop()弹出的是最后一个元素
// 2.从有序队列把元素弹出,同时要把元素从上面提到的map删除,因为不用再判断重复添加了
// 3. 将元素移到 通用队列中。 后续都只是等待时间
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 {
entry := waitingForQueue.Peek().(*waitFor)
// 计算等待第一个要添加元素的等待时间
// 有序队列时间是按照时间排序的,后续会等待更长的时间
nextReadyAt = q.clock.After(entry.readyAt.Sub(now))
}
// 等待
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
// 获取放入chan中的元素
case waitEntry := <-q.waitingForAddCh:
// 时间到 直接放入通用队列 时间未到,放入有序队列
if waitEntry.readyAt.After(q.clock.Now()) {
insert(waitingForQueue, waitingEntryByData, waitEntry)
} else {
q.Add(waitEntry.data)
}
drained := false
for !drained {
//取出chan中所有的元素,default当chan中没有数据就会立刻停止
select {
case waitEntry := <-q.waitingForAddCh:
if waitEntry.readyAt.After(q.clock.Now()) {
insert(waitingForQueue, waitingEntryByData, waitEntry)
} else {
q.Add(waitEntry.data)
}
default:
drained = true
}
}
}
}
}
// 优先级队列存在 更新时间;不存在,添加到优先级队列 和 map元素中
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
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
}
延时队列核心代码分析完了,主要是依赖golang的heap实现按照时间先后排序的数组,使得延时队列可以等待超时添加。
限速队列
限速器
抽象定义
type RateLimiter interface {
When(item interface{}) time.Duration // 返回该元素需要等待多长时间
Forget(item interface{}) // 去除该元素,表示已被处理
NumRequeues(item interface{}) int // 元素返回队列次数
}
BucketRateLimiter
利用golang.org.x.time.rate.Limiter实现固定速率(qps)的限速器。golang.org.x.time.rate.Limiter 后续go章节分析。
// BucketRateLimiter adapts a standard bucket to the workqueue ratelimiter API
type BucketRateLimiter struct {
*rate.Limiter
}
var _ RateLimiter = &BucketRateLimiter{}
func (r *BucketRateLimiter) When(item interface{}) time.Duration {
return r.Limiter.Reserve().Delay() // 获取延迟,相对固定的频率
}
func (r *BucketRateLimiter) NumRequeues(item interface{}) int {
return 0 // 固定频率,不存在重试
}
func (r *BucketRateLimiter) Forget(item interface{}) {
}
ItemExponentialFailureRateLimiter
指数失败次数增长限速器
// 指数级的 失败次数增长 baseDelay*2^<num-failures>
type ItemExponentialFailureRateLimiter struct {
failuresLock sync.Mutex // map元素的读写锁
failures map[interface{}]int // 元素失败次数记录
baseDelay time.Duration
maxDelay time.Duration
}
// 基本延时、最长延时
func NewItemExponentialFailureRateLimiter(baseDelay time.Duration, maxDelay time.Duration) RateLimiter {
return &ItemExponentialFailureRateLimiter{
failures: map[interface{}]int{},
baseDelay: baseDelay,
maxDelay: maxDelay,
}
}
// 最长时间1000S
func DefaultItemBasedRateLimiter() RateLimiter {
return NewItemExponentialFailureRateLimiter(time.Millisecond, 1000*time.Second)
}
func (r *ItemExponentialFailureRateLimiter) When(item interface{}) time.Duration {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
exp := r.failures[item]
// 失败次数+1
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)
}
ItemFastSlowRateLimiter
快慢限速器,元素失败重试次数作为界限。比较简单。
// ItemFastSlowRateLimiter does a quick retry for a certain number of attempts, then a slow retry after that
type ItemFastSlowRateLimiter struct {
failuresLock sync.Mutex
failures map[interface{}]int
maxFastAttempts int
fastDelay time.Duration
slowDelay time.Duration
}
var _ RateLimiter = &ItemFastSlowRateLimiter{}
func NewItemFastSlowRateLimiter(fastDelay, slowDelay time.Duration, maxFastAttempts int) RateLimiter {
return &ItemFastSlowRateLimiter{
failures: map[interface{}]int{},
fastDelay: fastDelay,
slowDelay: slowDelay,
maxFastAttempts: maxFastAttempts,
}
}
// 限速器 元素 失败次数 < 最快尝试次数时,延迟比较短
func (r *ItemFastSlowRateLimiter) When(item interface{}) time.Duration {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
r.failures[item] = r.failures[item] + 1
if r.failures[item] <= r.maxFastAttempts {
return r.fastDelay
}
return r.slowDelay
}
func (r *ItemFastSlowRateLimiter) NumRequeues(item interface{}) int {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
return r.failures[item]
}
func (r *ItemFastSlowRateLimiter) Forget(item interface{}) {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
delete(r.failures, item)
}
MaxOfRateLimiter
//所有限速器 返回最坏的情况
type MaxOfRateLimiter struct {
limiters []RateLimiter
}
func (r *MaxOfRateLimiter) When(item interface{}) time.Duration {
ret := time.Duration(0)
for _, limiter := range r.limiters {
curr := limiter.When(item)
if curr > ret {
ret = curr
}
}
return ret
}
func NewMaxOfRateLimiter(limiters ...RateLimiter) RateLimiter {
return &MaxOfRateLimiter{limiters: limiters}
}
func (r *MaxOfRateLimiter) NumRequeues(item interface{}) int {
ret := 0
for _, limiter := range r.limiters {
curr := limiter.NumRequeues(item)
if curr > ret {
ret = curr
}
}
return ret
}
func (r *MaxOfRateLimiter) Forget(item interface{}) {
for _, limiter := range r.limiters {
limiter.Forget(item)
}
}
限速队列
抽象定义
type RateLimitingInterface interface {
DelayingInterface // 延时队列
AddRateLimited(item interface{}) // 按照限速方式添加元素的接口
Forget(item interface{}) // 丢弃指定元素
NumRequeues(item interface{}) int // 查询元素放入队列的次数
}
// 这个是限速队列的实现
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)
}
func (q *rateLimitingType) Forget(item interface{}) {
q.rateLimiter.Forget(item)
}
通过限速器获取对象的延迟时间,然后通过延时方式放入队列,这样队列的内容就会按照我们要求的速率进入了。
ParallelizeUntil
// 并发 workers 处理协程、 pieces N个任务、doWorkPiece 任务
func ParallelizeUntil(ctx context.Context, workers, pieces int, doWorkPiece DoWorkPieceFunc) {
var stop <-chan struct{}
if ctx != nil {
stop = ctx.Done()
}
toProcess := make(chan int, pieces)
for i := 0; i < pieces; i++ {
toProcess <- i
}
close(toProcess)
if pieces < workers {
workers = pieces
}
wg := sync.WaitGroup{}
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer utilruntime.HandleCrash()
defer wg.Done()
// 处理pieces
for piece := range toProcess {
select {
case <-stop:
return
default:
doWorkPiece(piece)
}
}
}()
}
wg.Wait()
}
到这里,workqueue包的代码基本分析完了。