client-go源码分析--DeltaFIFO

前言

DeltaFIFO是client-go controller framework的重要环节,它的作用是保证Reflector和Indexer之间对象同步。可以说,DeltaFIFO是连接生产者(Reflector)和消费者(Indexer)的通道。阅读本文前希望读者已阅读《client-go源码分析》前面的系列文章,要求已了解Informer和Reflector。

1 DeltaFIFO介绍

DeltaFIFO的定义及创建:

type DeltaFIFO struct {
        。。。
        items map[string]Deltas
        
        queue []string

        keyFunc KeyFunc

        knownObjects KeyListerGetter
        。。。
}

func NewDeltaFIFO(keyFunc KeyFunc, knownObjects KeyListerGetter) *DeltaFIFO {
        f := &DeltaFIFO{
                items:        map[string]Deltas{},
                queue:        []string{},
                keyFunc:      keyFunc,
                knownObjects: knownObjects,
        }
        f.cond.L = &f.lock
        return f
}

以简单的示意图描述DeltaFIFO的结构:
client-go源码分析--DeltaFIFO_第1张图片

DeltaFIFO的核心结构包括items和queue,对应Delta和FIFO。

1.1 Delta

Delta顾名思义是变化和差异,因此items的作用是缓存某个对象的一系列变更行为(Deltas)。正因为这里保存的是Delta(当前对象和前一个对象的差异),有序地保存一系列行为才有意义,单独的某个Updated状态无法正确同步对象。
items是map结构,key是由keyfunc计算出来的对象key;value是deltas,即delta列表。delta数据结构如下:

type Delta struct {
        Type   DeltaType
        Object interface{}
}

当前版本DeltaType的值只有Added, Updated, Deleted, Sync;Object即runtime.Object。

1.2 FIFO

FIFO即先进先出队列,这里queue仅把对象键(objkey)入列。消费者通过objkey查找items的Deltas并依次同步所有行为。queue是对象同步处理队列,消费者仅处理queue中存在的objkey。

2 DeltaFIFO

基本功能上述已经提到DeltaType只有四种:Added, Updated, Deleted, Sync。这里仅分析最难理解的Sync函数,Sync是List机制和Resync触发的全量同步;Added,Updated,Deleted是Watch触发的增量同步。

2.1 深入理解Sync: Replace()

Sync是比较特殊的处理,理解了Sync也就不难理解其他行为。Sync是指Reflector全量同步Indexer,从DelaFIFO的实现看是调用Replace函数“重建”Indexer
DelaFIFO的Replace实现了Sync行为(Resync也调用该函数)。controller第一次启动时,Reflector的List机制触发DeltaFIFO的Sync行为,《Client-Go源码分析--Reflector》会提到r.syncWith(items,resourceVersion)函数,该函数调用func (f *DeltaFIFO) Replace(list []interface{},resourceVersion string) error开始分析Replace代码前,先看一个重要的公共函数queueActionLocked,DeltaFIFO的所有行为都会调用它。queueActionLocked的功能是:仅处理某个动作。若处理一系列动作或多个对象,需要外部调用者重复调用它。

func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {
        id, err := f.KeyOf(obj)
        if err != nil {
                return KeyError{obj, err}
        }

        // If object is supposed to be deleted (last event is Deleted),
        // then we should ignore Sync events, because it would result in
        // recreation of this object.
    // 上一次是deleted类型,且本次是sync,那么说明该对象已经删除了,直接返回nil
        if actionType == Sync && f.willObjectBeDeletedLocked(id) {
                return nil
        }

        newDeltas := append(f.items[id], Delta{actionType, obj})
    // delete去重,
        newDeltas = dedupDeltas(newDeltas)
    // item[id]不存在,加入到queue和items中
        if len(newDeltas) > 0 {
                if _, exists := f.items[id]; !exists {
                        f.queue = append(f.queue, id)
                }
                f.items[id] = newDeltas
                f.cond.Broadcast()
        } else {
                // We need to remove this from our map (extra items in the queue are
                // ignored if they are not in the map).
                delete(f.items, id)
        }
        return nil
}
// 外部同步缓存的func (f *DeltaFIFO) Replace(list []interface{}, resourceVersion string) error {
        f.lock.Lock()
        defer f.lock.Unlock()
        keys := make(sets.String, len(list))
    // 这里是k8s集群中的实际对象集合keys
        for _, item := range list {
                key, err := f.KeyOf(item)
                if err != nil {
                        return KeyError{item, err}
                }
                keys.Insert(key)
                if err := f.queueActionLocked(Sync, item); err != nil {
                        return fmt.Errorf("couldn't enqueue object: %v", err)
                }
        }

        if f.knownObjects == nil {
                // Do deletion detection against our own list.
                for k, oldItem := range f.items {
                        if keys.Has(k) {
                                continue
                        }
                        var deletedObj interface{}
                        if n := oldItem.Newest(); n != nil {
                                deletedObj = n.Object
                        }
                        if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
                                return err
                        }
                }

                if !f.populated {
                        f.populated = true
                        f.initialPopulationCount = len(list)
                }

                return nil
        }

        // Detect deletions not already in the queue.
    //knownKeys 是indexers中的对象集合,是缓存中的数据
        knownKeys := f.knownObjects.ListKeys()
        queuedDeletions := 0
        for _, k := range knownKeys {
                if keys.Has(k) {    // k8s集群和indexers缓存中的obj都存在,不处理
                        continue
                }
        // indexers中存在obj,但是k8s集群中不存在,删除该obj
                deletedObj, exists, err := f.knownObjects.GetByKey(k)
                if err != nil {
                        deletedObj = nil
                        klog.Errorf("Unexpected error %v during lookup of key %v, placing DeleteFinalStateUnknown marker without object", err, k)
                } else if !exists {
                        deletedObj = nil
                        klog.Infof("Key %v does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", k)
                }
                queuedDeletions++
                if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {   // Deleted的加入到DeltaFIFO中,删除indexers中的obj对象
                        return err
                }
        }

        if !f.populated {   // 如果populated还没有设置,说明是第一次 并且 还没有任何修改操作执行过
                f.populated = true
                f.initialPopulationCount = len(list) + queuedDeletions
        }

        return nil
}

2.2 queueActionLocked的特殊处理分析

上文提到的queueActionLocked特殊处理针对一种场景:obj的id是objkey1,该对象最后一次动作是Deleted,并且objkey1仍在queue中等待处理。此时周期性被动触发一次Resync(会调用Replace)。k8s集群真实的runtime.Object已删除,但Indexer的同步尚未完成,Indexer缓存里仍有这个对象。
client-go源码分析--DeltaFIFO_第2张图片

假设queueActionLocked更新了deltas如上图(左)所示,该对象会先删除,然后添加回来!从Pop()函数里的回调函数Process可以看到这个过程。前几篇文章已经提到过,Process的入参obj是Deltas队列,该函数依次处理每个Delta行为。当Process处理到上图(左)Deleted时Index会删除对象;当它处理到最后的Sync时,Index又会添加这个对象。因此,queueActionLocked通过特殊处理规避了这个问题,现有代码的处理方式如上图(右)所示,直接返回不更新deltas。

2.3 sync场景的DeleteFinalStateUnknown分析

// DeletedFinalStateUnknown isplaced into a DeltaFIFO in the case where
// an object was deleted but thewatch deletion event was missed. In this
// case we don't know the final"resting" state of the object, so there's// a chance the included Obj isstale.
这个对象困惑了我许久,从注释上看是针对Watch Deleted事件丢失,导致k8s实际对象和Indexer缓存不一致的场景。我们用图展示两种场景:1. Deleted事件未丢失,只是queue待处理该对象;2. Deleted事件丢失。client-go源码分析--DeltaFIFO_第3张图片

上图(左)是Deleted消息丢失的代码处理,queueActionLocked会添加DeleteFinalStateUnknown对象到deltas,并保存到Indexer。DeleteFinalStateUnknown是对runtime.Object的封装。上图(右)是Deleted消息未丢失的代码处理。deltas对象变化过程如下:
client-go源码分析--DeltaFIFO_第4张图片

从Process回调函数看,是直接调用clientState.Delete(d.Object)删除对象的,Indexer计算对象键是否会异常呢?抱着好奇心看了一眼keyFunc(计算对象键的函数,下一篇文章会讲到)DeletionHandlingMetaNamespaceKeyFunc:

func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) {
        if d, ok := obj.(DeletedFinalStateUnknown); ok {
                return d.Key, nil
        }
        return MetaNamespaceKeyFunc(obj)
}

DeletionHandlingMetaNamespaceKeyFunc果然有对DeletedFinalStateUnknown的处理(obj.(DeletedFinalStateUnknown)是类型断言用法),因此不需要担心获取对象键会异常。如果是我们自己实现keyFunc,一样要考虑到DeletedFinalStateUnknown这个而特殊对象。到此为止DeltaFIFO的主要机制已分析完毕。

参考

https://blog.csdn.net/li_101357/article/details/89763992

https://www.cnblogs.com/charlieroro/p/10330390.html

你可能感兴趣的:(kubernetes)