DeltaFIFO

Reflector调用List/Watch方法后,数据会存入DeltaFIFO这个先进先出的队列。

从名称上看,DeltaFIFO需要分2部分来理解,FIFO(First Input First Output,先进先出)是指和正常的先进先出队列一样有基本的操作方法,例如Add、Delete、Update、List、Pop等;Delta则是一个资源对象的存储,用来保存资源对象的操作类型。源码路径为k8s.io/client-go/tools/cache/delta_fifo.go。

DeltaFIFO的结构定义如下所示。

type DeltaFIFO struct {
    lock sync.RWMutex
    cond sync.Cond
    items map[string]Deltas
    queue []string
    populated bool
    initialPopulationCount int
    keyFunc KeyFunc
    knownObjects KeyListerGetter
    closed bool
    emitDeltaTypeReplaced bool
}
type Deltas []Delta
type Delta struct {
    Type    DeltaType
    Object interface{}
}

DeltaFIFO的结构中最主要的字段是queue、items以及Delta,我们看下这3个字段的数据类型。

·queue字段是一个string类型的切片,切片中存储的是资源对象的key,通过KeyOf函数得到。

·items字段是一个map数据结构,其key是queue字段中存储的资源对象的key,其值是一个Delta切片。

·Delta字段是一个结构体类型的切片,记录了事件的类型和产生这个事件的资源对象。

下图是DeltaFIFO的数据结构示意图。

DeltaFIFO数据结构 

作为一个先进先出的队列,DeltaFIFO最基本的功能是生产和消费消息,下面对这2个功能进行讲解。

1.消息的生产

queueActionLocked()方法定义了DeltaFIFO生产消息的主要流程,示例如下所示。

func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {
    id, err := f.KeyOf(obj)
    if err != nil {
        return KeyError{obj, err}
    }
    oldDeltas := f.items[id]
    newDeltas := append(oldDeltas, Delta{actionType, obj})
    newDeltas = dedupDeltas(newDeltas)
    if len(newDeltas) > 0 {
        if _, exists := f.items[id]; !exists {
            f.queue = append(f.queue, id)
        }
        f.items[id] = newDeltas
        f.cond.Broadcast()
    } else {
        if oldDeltas == nil {
            klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, 
                obj=%#+v; ignoring", id, oldDeltas, obj)
            return nil
        }
        klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; 
            breaking invariant by storing empty Deltas", id, oldDeltas, obj)
        f.items[id] = newDeltas
        return fmt.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, 
            obj=%#+v; broke DeltaFIFO invariant by storing empty Deltas", id, 
            oldDeltas, obj)
    }
    return nil
}

Added、Updated、Deleted、Replaced、Sync的事件中会调用queueActionLocked将资源对象追加到Delta的列表中,下面是queueActionLocked的主要流程解析。

1)通过KeyOf方法计算出对象的ID,通过ID取出oldDeltas列表。

2)将资源对象的操作类型和资源对象追加到oldDeltas列表。

3)使用dedupDeltas函数去重。

4)构建最新的Items后,通过cond.Broadcast()方法通知所有消费者取消阻塞。

这个方法的调用入口是Reflector,List/Watch流程中的r.syncWith()方法和r.watchHandler()方法就是队列生产的入口。

2.消息的消费

DeltaFIFO消息的消费流程在Pop()方法中,示例如下所示。

func (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()
        }
        id := f.queue[0]
        f.queue = f.queue[1:]
        if f.initialPopulationCount > 0 {
            f.initialPopulationCount--
        }
        item, ok := f.items[id]
        if !ok {
            klog.Errorf("Inconceivable! %q was in f.queue but not f.items; 
                ignoring.", id)
            continue
        }
        delete(f.items, id)
        err := process(item)
        if e, ok := err.(ErrRequeue); ok {
            f.addIfNotPresent(id, item)
            err = e.Err
        }
        return item, err
    }
}

Pop()方法定义了消息的消费过程,主要流程分析如下。

1)加锁,开启一个循环,如果队列中没有数据,则使用f.cond.Wait进行阻塞等待。

2)如果队列不为空,那么取出队列头部的数据并删除,将对象传入回调函数进行处理。

3)如果回调函数处理失败,将对象重新放入队列。

队列的消费由Controller的processLoop方法从DeltaFIFO队列中取出,并传递给回调函数。回调函数通过HandleDeltas()方法实现。源码位置为k8s.io/client-go/tools/cache/shared_informer.go。

HandleDeltas()方法的定义如下所示。

func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
    s.blockDeltas.Lock()
    defer s.blockDeltas.Unlock()
    for _, d := range obj.(Deltas) {
        switch d.Type {
        case Sync, Replaced, Added, Updated:
            s.cacheMutationDetector.AddObject(d.Object)
            if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
                if err := s.indexer.Update(d.Object); err != nil {
                    return err
                }
                isSync := false
                switch {
                case d.Type == Sync:
                    isSync = true
                case d.Type == Replaced:
                    if accessor, err := meta.Accessor(d.Object); err == nil {
                        if oldAccessor, err := meta.Accessor(old); err == nil {
                          
                            isSync = accessor.GetResourceVersion() == 
                                oldAccessor.GetResourceVersion()
                        }
                    }
                }
                s.processor.distribute(updateNotification{oldObj: old, newObj: 
                    d.Object}, isSync)
            } else {
                if err := s.indexer.Add(d.Object); err != nil {
                    return err
                }
                s.processor.distribute(addNotification{newObj: d.Object}, false)
            }
        case Deleted:
            if err := s.indexer.Delete(d.Object); err != nil {
                return err
            }
            s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
        }
    }
    return nil
}

HandleDetal除了将事件和对象存入Indexer以外,HandlerDetal还会将数据通过distribute()函数分发到ShareInformer,这样用户在使用informer.AddEventHandler函数时才会收到事件的通知并触发回调。

posted @ 2023-02-26 14:27  muzinan110  阅读(38)  评论(0编辑  收藏  举报