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函数时才会收到事件的通知并触发回调。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)