WIP: DeltaFIFO

Queue 接口抽象出一个生产者消费者队列,用来解耦对 kubernetes 资源的获取和处理:

  • Reflector 作为生产者,将从 apiserver 中获取到的资源对象存放到 Queue 中
  • Controller 作为消费者,调用 Pop 方法进行消费。
    在这里插入图片描述

Queue 在 Store 接口的基础上又提供了四个方法用于队列的消费

type Store interface {
	Add(obj interface{}) error
	Update(obj interface{}) error
	Delete(obj interface{}) error
	List() []interface{}
	ListKeys() []string
	Get(obj interface{}) (item interface{}, exists bool, err error)
	GetByKey(key string) (item interface{}, exists bool, err error)
	Replace([]interface{}, string) error
	Resync() error
}
type Queue interface {
	Store

	Pop(PopProcessFunc) (interface{}, error)
	AddIfNotPresent(interface{}) error
	HasSynced() bool
	Close()
}

Store 主要用于对数据的增删改查,而 Queue 增加了:

  • Pop 从 Queue 中获取一个对象,并调用 PopProcessFunc。
    如果 Queue 为空则会阻塞等待,当 Queue 被关闭后,阻塞的 Pop 操作就会返回 ErrQueueClose
  • AddIfPresent 将对象放回到 Queue 中,不过如果 Queue 已经存在相同对象(使用 KeyFunc 计算出相同的 key)的话,那么就不做任何操作
  • Close 关闭 Queue,在 DeltaFIFO 和 FIFO 的实现中,Close 只会影响 Pop 方法,并不会影响其他操作
  • HasSynced 如果通过 Replace 操作添加到 Queue 的数据全部都 Pop 了,那么就返回 True。
    需要注意 Replace 只有发生在 Add, AddIfNotPresent, Update, Delete 操作之前才会影响到 HasSynced,如果先调用了 Add 之类的方法,然后再调用 Replace ,无论是否 Pop 数据,HasSynced 总是为 True

并且 Pop 方法会根据 PopProcessFunc 的返回值来决定是否调用 AddIfPresent 将对象重新入队,如果返回 ErrRequeue 错误的话,会将该对象重新入队。

client-go 为 Queue 接口提供了两种实现:DeltasFIFO 和 FIFO,我们通常会使用 DeltasFIFO,而 FIFO 提供的功能通常不能满足我们在 Informer 中的需求
DeltasFIFO 和 FIFO 虽然都实现了 Queue 的接口,但是 DeltasFIFO 和 FIFO 在使用上却是完全不一样?
我们先详细看一下 DeltasFIFO 的实现

DeltasFIFO

DeltasFIFO 并不是正常的先入先出队列,为什么呢,因为 DeltasFIFO 中并不是直接存放入队的对象,我们可以先看一下 DeltasFIFO 存放数据的字段

type DeltasFIFO {
	lock sync.RWMutex	// 保证并发安全
	cond sync.Condition	// 用来通知 Pop 操作,队列中有新入队的数据
	
	items map[string]Deltas
	queue []string
	
	keyFunc KeyFunc
}

queue 记录了入队的顺序,items 中存放的根据 key 来去重的数据

和所有的 Store 一样,DeltasFIFO 根据 keyFunc 返回的对象向的 key,来判断是否是相同的对象

这里可以发现 items 的值是 Deltas 对象,而不是直接保存 interface{} 对象
我们来看一下 Deltas 到底是什么

type DeltaType string

const (
	Added DeltaType = "Added"
	Updated DeltaType = "Updated"
	Deleted DeltaType = "Deleted"
	
	Replaced DeltaType = "Replaced"
	Sync DeltaType = "Sync"
)

type Delta struct {
	Type DeltaType
	Object interface{}
}

type Deltas []Delta

DeltaFIFO 在将 Object 入队时会封装成 Delta 用来表示对 Object 的具体操作,然后根据 keyFunc 计算出 Object 相应的 key, 再将 Delta 存放到 key 对应的 Deltas 中。
这意味着 DeltaFIFO 中保存的是对 Object 的一系列操作,而不是单个的 Object
在这里插入图片描述

DeltaType 表示对 Object 执行的操作,也对应着 DeltaFIFO 不同的入队方法

MethodDeltaType
Add(object interface{})Added
Update(object interface{})Updated
Delete(obj interface{})Deleted
Replace(obj []interface{})(Replaced / Sync) 和 Deleted
Resync(obj interface{})Sync

Replace 方法对应了 Replaced 和 Sync 两种 DeltaType,这是 DeltaFIFO 为了兼容性而设计的,具体我们后续会详细讲述

可以发现 Delete 操作并不是删除队列中的数据,而是生成一个 Deleted 类型的 Delta 添加到 Deltas 中。
这也是 DeltaFIFO 的一个特点:会把所有对 object 的增删改操作都认为是一个事件

Queue 的另一个实现 FIFO 的 Delete 操作便是删除队列中的对象

基本入队操作:Add, Update

我们先从最简单的 Add 和 Update 开始深入
当调用 DeltaFIFO 的 Add,Update 方法时会将需要入队的对象转换成 Delta 对象

func (f *DeltaFIFO) Add(obj interface{}) error {
	f.lock.Lock()
	defer f.lock.Unlock()
	f.populated = true	// 与 HasSync 操作有关
	return f.queueActionLocked(Added, obj)
}

func (f *DeltaFIFO) Update(obj interface{}) error {
	f.lock.Lock()
	defer f.lock.Unlock()
	f.populated = true	// 与 HasSync 操作有关
	return f.queueActionLocked(Updated, obj)
}

Add,Update 都是调用 queueActionLocked 方法,只是设置的 DeltaType 不同

func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {
	// Step 1: 计算 obj 的 key
	id, err := f.KeyOf(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	
	// Step 2: 获取该对象的 Delta 列表,然后将 Object 生成的 Delta 添加到列表中,并做去重操作
	oldDeltas := f.items[id]
	newDeltas := append(oldDdeltas, Delta{actionType, obj})
	newDeltas = dedupDeltas(newDeltas)
	
	// Step 3: 更新该对象的 Delta 列表,如果 queue 队列中已经记录了该对象待出队,那么就不会重复添加到 queue 中
	if len(newDeltas) > 0{
		if _, exists := f.items[id]; !exists {
			f.queue = append(f.queue, id)
		}
		f.items[id] = newDeltas
		f.cond.Broadcast() // 入队广播
	}
	return nil
}

入队操作可以可以分为三个阶段:

  1. 通过 KeyOf 函数计算出 object 的 key,通过这个 key 来判断是否是同一个对象

KeyOf 中并不是直接调用 keyFunc 函数,而是先判断 objecct 的对象是否是 Deltas 或者 DeletedFinalStateUnknown

这里先不详细介绍 DeletedFinishalStateUnknow 是什么,只要知道 DelteFinishalStateUnknown 也算是一种 Delta,用来表示删除一个已删除对象的操作,只会在 Replace 操作中生成该对象

func (f *DeltaFIFO) KeyOf(obj interface{}) (string, error) {
	if d, ok := obj.(Deltas); ok {
		if len(d) == 0 {
			return "", KeyError{obj, ErrZeroLengthDeltasObject}
		}
		// Newest 会返回 Deltas 中最新(最后)一个 Delta
		obj = d.Newest().Object
	}
	if d, ok := obj.(DeletedFinalStateUnknow); ok {
		return d.Key, nil
	}
	return f.keyFunc(obj)
}
  1. 根据 key 从 items 中获取该对象的 Deltas 列表,并将新生成的 Delta{actionType, obj} 添加到 Deltas 列表中

在添加到列表后,会调用 dedupDeltas 函数对 Deltas 列表进行去重,实际是对新添加的这个 Delta 和 之前的 Delta 进行比对

func dedupDeltas(deltas Deltas) Deltas {
	// 如果当前 Deltas 中只有一个 Delta,那么就没有必要去重了
	n := len(deltas)
	if n < 2 {
		return deltas)
	}

	// a 是最新添加的 Delta,b 是上一个 Delta
	a,b := &deltas[n-1], &deltas[n-2]
	if out := isDup(a, b); out != nil {
		// isDup 返回不为 nil, 说明 a 和 b 被去重了一个
		d := append(Deltas{}, deltas[:n-2]...)
		// 将 isDup 返回的 Delta 添加到 Deltas 中
		return append(d, *out)
	}
	return deltas
}

isDup 函数的作用便是比对两个 Delta 是否可以被去重,并且返回需要保留的那个 Delta

func isDup(a, b *Delta) *Delta {
	if out := isDeletionDump(a, b); out != nil{
		return out
	}
	return nil
}

func  isDeletionDup(a, b *Delta) *Delta{
	if b.Type != Deleted || a.Type != Deleted {
		return nil
	}
	if _, ok := b.Object.(DeletedFinalStateUnknow); ok {
		return a
	}
	return b

实际 isDup 只会对两个 DeltaType 都是 Deleted 的 Delta 进行去重,只保留一个 Deleted 的 Delta

  1. 判断 items 中是否已经存在该对象的 Deltas 列表,如果不存在就将 object 的 key 添加到 queue 队列中,最后将去重后的 Deltas 列表放入 items 中。
		if _, exists := f.items[id]; !exists {
			f.queue = append(f.queue, id)
		}
		f.items[id] = newDeltas
		f.cond.Broadcast()

如果 items 没有该对象的 Deltas 列表也就意味着,该对象还没有加入到 queue 队列中。
入队完成,就会调用 f.cond.Broadcast 来通知阻塞等待的 Pop 操作,Pop 操作会根据 queue 的顺序来取出对象,后续会详细讲述 Pop 的操作流程

DeltaFIFO 和 外部存储

上文介绍了 Add 和 Update, 这两个操作就是简单的将 Object 封装成相应的 Delta,然后进行入队操作。
DeltaFIFO 还提供了关联一个实现了 KeyListerGetter 接口的外部存储的功能,而这个外部存储会影响到 Delete, Replace, Resync 操作的行为

type KeyListerGetter interface {
	ListKeys() []string
	GetByKey(key string) (value interface{}, exists bool, err error)
}

Store 接口包含了 KeyListerGetter 接口要求的方法

这里需要注意 KeyListerGetter 同样是以 key 的方式来辨别是否是同一对象,这也就是说 KeyListerGetter 的 key 生成规则必须和 DeltaFIFO 一样,换句话说就是他们的 KeyFunc 必须相同

文章开头那张图是以 Queue 的形式来画的,那么现在我们用 DeltaFIFO 替换掉 Queue 来看一下 Controller 的架构图

通常外部存储都是 Store 或者 Indexer 以 KeyListerGetter 的形式提供给 DeltaFIFO

在这里插入图片描述

KeyListerGetter 作为 knowObject 字段存在于 DeltaFIFO 中

type DeltaFIFO struct {
	lock sync.RWMutex
	items map[string] Deltas
	queue []string
	keyFunc KeyFunc
	
	knownObjects KeyListerGetter
}

那 knownObject 是怎么设置到 DeltaFIFO 中的呢?我们可以先看一下如何创建一个 DeltaFIFO

import "k8s.io/client-go/tools/cache"
func main(){
	f := cache.NewDeltaFIFOWithOptions(
		cache.DeltaFIFOOptions{
			KeyFunction: keyFunc,
			KnownObjects: store,
		}
	)
}

KnownObjects 便是用来设置外部存储 KeyGetterLister 的

上面说了 knownObjects 会影响到 Delete, Replace, Resync 的行为,我们先来看一下 Delete 操作

func (f *DeltaFIFO) Delete(obj interface{}) error {
	id, err := f.KeyOf(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	
	f.populated = true	// 与 HasSync 操作有关
	if f.knownObjects == nil {
		if _, exists := f.items[id]; !exists {
			return nil
		}
	} else {
		// 直接使用 GetByKey 来调用 GetByKey 也就意味着 knownObjects 计算 key 的方式必须和 DeltaFIFO 一致
		_, exists, err := f.knownObjects.GetByKey(id)
		_, itemsExists := f.items[id]
		if err == nil && !exists && !itemsExist {
			return nil
		}
	}
	return f.queueActionLocked(Deleted, obj)
}

Delete 方法虽然最后也会调用 queueActionLocked 来入队一个 Deleted 类型的 Delta,但是在入队前会判断队列或者外部存储中是否还存在该对象,如果不存在的话那么也就没有入队的 Deleted 类型的 Delta 的必要了

  1. 如果没有外部存储(knownObjects == nil),那么判断队列中是否有该对象(f.items[id]),如果没有那么直接返回
  2. 如果有外部存储,并且队列和外部存储中都没有该对象了,那么就可以直接返回

Resync:将外部存储中的对象重新入队

我们接下来介绍一下 Resync 操作,Resync 是完全依赖于 knownObjects,通过 knownobjects 的 ListKeys 和 GetByKey 方法将外部存储中所有对象以 Sync 类型重新入队到队列中。
当然如果队列中已经存在该对象时,那么就不会执行重新入队的操作

func (f *DeltaFIFO) Resync() error {
	f.lock.Lock()
	defer f.lock.Unlock()

	// 如果没有外部存储,那么直接返回
	if f.knownObjects == nil {
		return nil
	}
	
	// 获取外部存储中所有的 key
	keys := f.knownObjects.ListKeys()
	for _, k := range keys {
		if err := f.syncKeyLocked(k); err != nil {
			return err
		}
	}
	return nil
}

func (f *DeltaFIFO) syncKeyLocked(key string) error {
	obj, exixts, err := f.knownObjects.GetByKey(key)
	if err != nil || !exists {
		return nil
	}

	// 这里会调用自己的 keyFunc 来计算出在队列中的 key
	id, err := f.KeyOf(obj)
	if err != nil {
		return KeyError{obj, err}
	}
	// 如果队列中已经存在该对象的 Deltas, 那么不会重新入队
	if len(f.items[id] > 0 {
		return nil
	}
	
	if err := f.queueActionLocked(Sync, obj); err != nil {
		return fmt.Errorf("coudn't queue object: %V", err)
	}
	return nil

Resync 逻辑比较简单,从 knownObjects 中获取所有对象,然后根据队列中是否已经存在该对象的 Deltas 列表来决定是否真正入队一个 Sync 类型的 Delta

这里需要注意的是,DeltaFIFO 根据 knownObjects 返回的 obj 重新计算了一下队列的 key,看起来很好
实际上在 Delete 和 Replace 操作中已经限制了 knownObjects 必须和 DeltaFIFO 的 KeyFunc 一样,也就是并没有必要去重新计算 key

Replace: 设置 DeltaFIFO 初始数据

Replace 会根据创建 DeltaFIFO 使 DeltaFIFOOptions.EmitDeltaTypeReplaced 来决定入队的 Delta 的 DeltaType,默认 DeltaType 是 Sync

type DeltaFIFOOptions struct {
	KeyFunction KeyFunc
	KnownObjects KeyGeterLister
	
	// 设置为 True, Replace 操作会成成 Replaced Delta
	EmitDeltaTypeReplaced bool
}

type DeltaFIFO struct {
	// ...
	emitDeltaTypeReplaced bool
}

Replace 会根据是否有外部存储 knownObjects 而有不同的行为,不过他们首先都会将 Replace 的所有对象先加入到队列中

// resourceVersion 参数不会用到
func (f *DeltaFIFO) Replace(list []interfavce{}, resourceVersion string) error {
	f.lock.Lock()
	defer f.lock.Unlock()
	keys := make(sets.String, len(lists))

	action := Sync
	if f.emitDeltaTypeReplaced {
		action = Replaced
	}
	
	for _, item := range list {
		key, err := f.KeyOf(item)
		if err != nil {
			return KeyError{item, error}
		}
		keys.Insert(key)
		if err := f.queueActionLocked(action, item); err != nil {
			return fmt.Errorf("couldn't enqueue object: %v", err)
		}
	}
  1. 根据 f.emitDeltaTypeReplaced 判断入队的 DeltaType 是 Sync 还是 Replaced
  2. 调用 queueActionLocked 进行入队操作

这里可以看到和 Sync 操作的不同,Replace 的所有对象都会被添加到队列中,不需要判断队列中是否已经存在该对象

对象入队后,会根据队列中的对象或者外部存储 knownObjects 中的对象来生成 Deleted 类型的 Delta

没有外部存储 knownObjects 的 Replace 操作
	if k.knownObjects == nil {
		queuedDeletions := 0
		for k, oldItem := range f.items {
			// keys 是 Replace 的所有对象的 key
			// k 代表着队列中当前存在的 key
			// 如果 keys 中不包含 k,也就意味着 Replace 的对象并没有 k 所对应的对象,这时就需要生成一个 Deleted 的 Delta 来创建一个删除事件
			if keys.Has(k) {
				continue
			}
			
			var deleteObj interface{}
			if n := oldItem.Newest(); n != nil {
				deletedObj = n.Object
			}
			// 计数生成的 Deleted Delta
			queuedDeletions++
			if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
				return err
			}
		}
		// 如果 populated 不为 False,说明在 Replace 前已经执行过其他入队的操作,这时 Replace 的数据就不能算作初始数据,也就不会设置 initialPopulationCount
		// 而 initialPopulationCount 字段和 HasSync 相关
		if !f.populated {
			f.populated = true
			f.initialPopulationCount = keys.Len() + queuedDeletions
		}
		return nil
	}

Replace 操作会比对 Replace 的对象和队列中当前存在的对象,然后为那些不存在于 Replace 的对象生成 Deleted Delta。
Rpllace 生成的这些 Deleted Delta 的 Object 是 DeleteFinalStateUnknown 对象。

Replace 操作又涉及到一个新的字段 initialPopulationCount,这个字段和 populated 一样都是用于 HasSync 方法
populated 会被 Add,Update,Delete 操作时置为 true,如果 Replace 前没有进行这三种入队操作和 Replace 操作,那么 Replace 的对象列表就可以被认为是初始数据,会将 initialPopulationCount 设置为 Replace 操作入队的 Delta 数量(包括用于删除对象生成的 Deleted Delta)

type DeltaFIFO struct {
	// ...
	populated bool
	initialPopulationCount int
}

func (f *DeltaFIFO) HasSync() bool {
	f.lock.Lock()
	defer f.lock.Unlock()
	return f.populated && f.initialPopulationCount == 0 
}

如果 initialPopulationCount 大于 0 , Pop 会递减该字段,下文会详细讲述

现在来看一下 DeltaFIFO 中存在 knownObject 的情况

具有外部存储 knownObjects 的 Replace 操作
	queuedDeletions := 0
	for _, k := range f.knownObjects.ListKeys() {
		// 判断 replace 的数据中是否包含 knownObjects 中已有的数据,如果不包含就生成 Deleted Delta
		if keys.Has(key)
			continue
		}
	
		deletedObj, exists, err := f.knownObjects.GetByKey(k)
		if err != nil || !exists {
			deletedObj = nil
		}
		queuedDeletions++
		if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
			return err
		}
	}
	if !f.populated {
		f.populated = tgrue
		f.initialPopulationCount = keys.Len() + queueDeletions
	}

具有 knownObjects 的 DeltaFIFO 的操作流程和没有 knownObjects 的 DeltaFIFO 一样,目的都是将不在 Replace 的对象列表中的对象删除,也就是生成一个 Deleted Delta。

DeletedFinalStateUnknown

Replace 会生成两种 Delta,一种是为 Replace 对象列表生成 Sync/Replace 的 Delta,另外一种就是为了删除那些不在 Replace 对象列表中的对象而生成的 Deleted Delta

这些生成 Delted Delta 的 Object 都是 DeletedFinalStateUnknown 对象

type DeltedFinalStateUnknown struct {
	Key string
	Obj interface{}
}

为什么会需要将要删除的对象再次封装到 DeletedFinalUnknown 中,而不是直接作为 Delta 的 Object 呢?

  1. 区分 Delete 和 Replace 创建的 Deleted Delta
  2. 由于某些问题导致,无法获取到具体对象时,依然可以将 Key 保存到 DeletedFinalStateUnknown,这样就可以通过 key 来知道 Deleted Delta 对应的对象是谁

那什么情况下无法获取准备删除的对象呢

  1. knownObjects == nil 时,被删除的对象是通过 deletedItem.Newest() 从 Deltas 中获取到的,如果 deleteditem 为空的,那么就会返回 nil。

在当前 client-go 的实现中 items 中的 Deltas 列表不可能为空

  1. knownObjects != nil 时,被删除对象是用 knownObjects.GetByKey 来获取的,而该对象刚刚被删除了,那么也会返回 nil。

也就是说 DeletedFinalStateUnknown 只有刚好在 knownObjects.ListKeysknownObject.GetByKey 之间这段时间被删除了才会出现,在调用 Pop 来处理数据时依然要注意,Deltas 中还是可能存在 DeletedFinalStateUnknown 对象的,需要对这种情况进行判断处理

DeletedFinalStateUnknown.Obj 也可能是 DeletedFinialStateUnknown 对象,连续多次 Replace 操作就可能产生这样的效果

Pop:使用 PopProcessFunc 串行处理队列数据

Pop 会对 DeltaFIFO 出队的对象执行 PopProcessFunc

func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {
	f.lock.Lock()
	defer f.lock.Unlock()

	for {
		// 如果当前队列为空,那么就阻塞等待,如果队列已经关闭了,那么就返回 ErrFIFOClosed
		for len(f.queue) == 0 {
			if f.closed {
				return nil, ErrFIFOClosed
			}
			// 入队数据或者关闭队列都会调用 f.cond.Broadcast
			f.cond.Wait()
		}
		
		// 出队数据
		id := f.queue[0]
		f.queue = f.queue[1:]
		if f.initialPopulationCount > 0 {
			f.initialPopulationCount--
		}
		item, ok := f.items[id]
		if !ok {
			continue
		}
		delete(f.items, id)

		// 调用 PopProcessFunc
		// 如果 PopProcessFunc 返回 ErrRequeue,则重新入队
		err := process(item)
		if e, ok := err.(ErrRequeue); ok {
			f.addIfNotPresent(id, item)
			err = e.Err
		}
		return item, err
}

Pop 操作在队列没有关闭,并且队列为空的时候是会阻塞等待数据

DeltaFIFO 出队的数据是 Deltas 对象,PopProcessFunc 需要特别注意传入的对象时 Deltas 对象。
另外 PopProcessFunc 是在加锁的情况下执行的,也就是说 PopProcessFunc 操作是一个串行操作,所以 PopProcessFunc 需要尽快的执行完成,不能长时间阻塞。
PopProcessFunc 不能调用会对 DeltaFIFO 加锁的操作,比如 Add,AddIfNotPresent,Update 之类的操作

Pop 操作在 initialPopulationCount > 0 时,递减 initialPopulationCount,当 initialPopulationCount 减为 0 时,意味着通过 Replace 添加的初始数据都已经 Pop 出队并使用 PopProcessFunc 处理完成了。

AddIfNotPresent 重新入队

AddIfPressent 操作通常用于将从 Pop 中获取到的 Deltas 重新入队到 DeltaFIFO 中,不过如果队列中已经存在了相同对象的 Deltas 的话,那么就不会再入队了

func (f *DeltaFIFO) AddIfNotPresent(obj interface{}) error {
	deltas, ok := obj.(Deltas)
	if !ok {
		return fmt.Errorf("object must be of type deltas, but got: %#v", obj)
	}
	id, err := f.KeyOf(deltas.Newest().Object)
	if err != nil {
		return KeyError{obj, error}
	}
	f.lock.Lock()
	defer f.lock.Unlock()
	f.addIfNotPresent(id, deltas)
	return nil
}

func (f *DeltaFIFO) addIfNotPresent(id string, deltas Deltas) {
	f.populated = true
	if _, exists := f.items[id]; exists {
		return
	}
	
	f.queue = append(f.queue, id)
	f.items[id] = deltas
	f.cond.Broadcast()
}

AddIfPresent 用于 PopProcessFunc 之外来进行重新入队操作,如果在 PopProcessFunc 内想要重新入队的话,应该返回 ErrRequeue,让 Pop 方法自动调用 addIfNotPresent 来重试
在 PopProcessFunc 中调用 AddIfPresent 会由于重复加锁永久阻塞

HasSync: DeltaFIFO 初始数据处理完成

上文已经简单介绍过 populatedinitialPopulationCount 两个字段
每次入队操作都会将 populated 置为 true,这些入队操作包括:Add, Delete, Update, AddIfNotPresent, Replace
ReSync 作为同步操作,并不会将 populated 置为 true

initialPopulationCount 会在 Replace 中设置,然后在 Pop 方法中递减,直到为 0

populated 为 True, 并且 initialPopulationCount 为 0 时,HasSync 就会返回 True,表示由 Replace 添加的初始数据都已经被 PopProcessFunc 消费完成了

type DeltaFIFO struct {
	// ...
	populated bool
	initialPopulationCount int
}

func (f *DeltaFIFO) HasSync() bool {
	f.lock.Lock()
	defer f.lock.Unlock()
	return f.populated && f.initialPopulationCount == 0 
}

Close: 关闭 DeltaFIFO

DeltaFIFO 的关闭操作实际只会影响到 Pop 操作,并不会影响到其他操作。
向一个关闭的 DeltaFIFO 执行入队的操作,依然可以入队成功,并且 Pop 操作如果发现队列不为空,那么也不会返回 ErrQueueClosed,依然会使用 PopProcessFunc 来处理出队的 Deltas

type DeltaFIFO {
	// ...
	closed bool
}

type (f *DeltaFIFO) Close() {
	f.lock.Lock()
	defer f.lock.Unlock()
	f.closed = true
	f.cond.Broadcast()
}

type (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {
	f.lock.Lock()
	defer f.lock.Unlock()
	for {
		for len(f.queue) == 0 {
			if f.closed {
				return nil, ErrFIFOClosed
			}
			f.cond.Wait()
		}
		// ...
	}
}

总结

上文分别介绍了 DeltaFIFO 结构的字段,我们现在完整的看一下 DeltaFIFO 的结构

type DeltaFIFO struct {
	lock sync.RWMutex // 用来保证 items 和 queue 的线程安全
	cond sync.Cond	// 通知 Pop 有入队的数据
	
	items map[string]Deltas	// 存放相同对象的 Deltas 列表,表示对象的不同操作
	queue []string	// 记录对象的先入先出顺序

	populated bool	// 表示已经执行过入队操作
	initialPopulationCount int	// Replace 操作涉及的对象数量
	keyFunc KeyFunc	// 用来计算对象的 key,使用 key 来区分对象

	knownObjects KeyListerGetter	// 外部存储会以 KeyListerGetter 接口的形式关联到 DeltaFIFO
	closed bool	// DeltaFIFO 是否关闭
	
	emitDeltaTypeReplaced bool	// Replace 操作生成的 DeltaType 是 Replace 还是 Sync,默认为 False,也就是 Sync

DeltaFIFO 的特殊之处在于他的 Add, Update, Delete 不是直接操作队列中的数据,而是将对象以事件的形式保存为 Delta 结构,然后入队到 DeltaFIFO 的相同对象的事件队列 Deltas 中。
对象是否相同是通过 keyFunc 生成的 key 来判断的,如果 key 相同而具体对象内数据不同,那么就可以当做是相同对象的不同版本

DeltaFIFO 中一共会操作四种对象:

对象描述DeltaFIFO 提供的方法
interface{}(Object)interface{} 类型的对象便是使用者真正要操作的对象AddDelete, Update
Delta将 Object 根据操作封装为 Delta 对象不提供直接操作 Delta 的方法
Deltas一组相同对象的 Delta,代表发生在 Object 上的不同操作AddIfNotPressent, Pop(PopProcessFunc)
DeletedFinalStateUnknow由 Relace 操作产生的 Object,会作为 Delted Delta 的 Object可以使用 Add,Delete,Update,但是不应该这么做,因为 DeltedFinalStateUnknow 属于内部生成的对象
Note:使用 DeltaFIFo 需要注意的地方:
  • 外部存储 knownObjects 使用的 KeyFunc 必须和 DeltaFIFO 保持一致不然 ResyncDelete 操作会出现异常
  • 如果在 Replace 之前调用过 Add, Update, Delete, AddIfNotPresent ,那么 HasSync 会永远返回 True
  • Pop 的出队处理函数 PopProcessFunc 内不能调用对 DeltaFIFO 加锁的操作,比如 AddClose 等等
  • PopProcessFunc 参数是 Deltas 对象,处理函数在执行时会对 DeltaFIFO 加锁,所以是以串行的方式执行,而且不能长时间执行,会阻塞其他操作
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值