kubernetes之client-go基础包workqueue

目录

目录

为什么介绍队列

通用队列

通过接口定义

类型定义

初始化

队列基本方法

简单的DEMO程序示例

延时队列

类型定义

初始化

延迟队列方法

限速队列

限速器

抽象定义

BucketRateLimiter

ItemExponentialFailureRateLimiter

ItemFastSlowRateLimiter

MaxOfRateLimiter

限速队列

抽象定义

实现方法

ParallelizeUntil


为什么介绍队列

如下图示:

可以看出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呢?

  1. set类型肯定最快,数组需要遍历效率太低,查找。
  2. 重复添加的场景
// 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包的代码基本分析完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值