Table of Contents
1.章节介绍
从上一章节可以知道。利用informer机制可以非常简单地实现一个资源对象的控制器,具体步骤为
(1)new SharedInformerFactory实例,然后指定indexer,listWatch参数,就可以生成一个 cache.SharedIndexInformer 对象
(2)cache.SharedIndexInformer 实际是完成了下图中的informer机制
这一章节开始从SharedIndexInformer入手研究informer机制。
2. cache.SharedIndexInformer结构介绍
type sharedIndexInformer struct { indexer Indexer // 本地的缓存+索引机制,上一篇文章详解介绍了 controller Controller // 控制器,启动reflector, 这个controller包含reflector:根据用户定义的ListWatch方法获取对象并更新增量队列DeltaFIFO processor *sharedProcessor // 注册了add,update,del事件的listener集合 cacheMutationDetector CacheMutationDetector // 突变检测器 // This block is tracked to handle late initialization of the controller listerWatcher ListerWatcher // 定义了list, watch函数, 看podinformer那里就可以知道,是直接调用了client往apiserver发送了请求 objectType runtime.Object // 定义要List watch的对象类型。如果是Podinfomer,就是要传入core.v1.pod // resyncCheckPeriod is how often we want the reflector's resync timer to fire so it can call // shouldResync to check if any of our listeners need a resync. resyncCheckPeriod time.Duration // 给自己的controller的reflector每隔多少s<尝试>调用listener的shouldResync方法 // defaultEventHandlerResyncPeriod is the default resync period for any handlers added via // AddEventHandler (i.e. they don't specify one and just want to use the shared informer's default // value). defaultEventHandlerResyncPeriod time.Duration // 通过AddEventHandler注册的handler的默认同步值 // clock allows for testability clock clock.Clock started, stopped bool startedLock sync.Mutex // blockDeltas gives a way to stop all event distribution so that a late event handler // can safely join the shared informer. blockDeltas sync.Mutex }
SharedIndexInformer主要包括以下对象:
(1)indexer
图中右下角的indexer。上一节已经分析了具体的实现。
(2)Controller
图中左边的Controller,启动reflector, list-watch那一套机制。接下来重点分析
(3)processor
图中最下面的listeners,所有往 informer注册了 ResourceEventHandler的都是一个listener。
因为是共享informer,所以存在一个inforemr实例化了多次,然后注册了多个ResourceEventHandler。一般情况下,一个Informer一个listener
type sharedProcessor struct { listenersStarted bool listenersLock sync.RWMutex listeners []*processorListener // 记录了informer添加的所有listener syncingListeners []*processorListener // 记录了informer中哪些listener处于sync状态。由resyncCheckPeriod参数控制。每隔resyncCheckPeriod秒,listener都需要重新同步一下,同步就是将listener变成syncingListeners。 clock clock.Clock wg wait.Group }
ResourceEventHandler结构体如下。这个就是定义Informer,add, update, del的处理事件。
type ResourceEventHandler interface { OnAdd(obj interface{}) OnUpdate(oldObj, newObj interface{}) OnDelete(obj interface{}) }
(4)CacheMutationDetector
突变检测器,用来检测内存中对象是否发生了突变。测试的时候用,默认不开启。这个先不深入了解
3. sharedIndexInformer.Run
k8s.io/client-go/tools/cache/shared_informer.go
在使用informer的时候,定义好sharedIndexInformer后,就直接运行了sharedIndexInformer.Run函数开始了整个Informer机制。
整个informer的运转逻辑就是:
(1)deltaFIFO接收listAndWatch的全量/增量数据,然后通过pop函数发送到HandleDeltas函数中 (生产)
(2)HandleDeltas将一个一个的事件发送到自定义的handlers 和 更新indexer缓存 (消费)
现在就沿着 Run这个函数入手,看看具体是如何实现的。sharedIndexInformer.Run主要逻辑如下:
-
new一个 deltafifo对象,并且指定对象的keyfun为 MetaNamespaceKeyFunc,就是用 ns/name 来当对象的key
-
生成config,利用config 生成一个controller
-
运行用户自定义handler的处理逻辑,s.processor.run (开启消费)
-
运行controller.run,实现整体的运作逻辑 (开启生产)
func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) { defer utilruntime.HandleCrash() // 1. new一个 deltafifo对象,并且指定对象的keyfun为 MetaNamespaceKeyFunc,就是用 ns/name 来当对象的key fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, s.indexer) // 2. 生成config cfg := &Config{ Queue: fifo, ListerWatcher: s.listerWatcher, ObjectType: s.objectType, FullResyncPeriod: s.resyncCheckPeriod, // 同步周期 RetryOnError: false, ShouldResync: s.processor.shouldResync, // 这是个函数,用于判断自定义的handler是否需要同步 Process: s.HandleDeltas, // listwatch来了数据,如何处理的函数 } func() { s.startedLock.Lock() defer s.startedLock.Unlock() // 3. 利用config 生成一个controller s.controller = New(cfg) s.controller.(*controller).clock = s.clock s.started = true }() // Separate stop channel because Processor should be stopped strictly after controller processorStopCh := make(chan struct{}) var wg wait.Group defer wg.Wait() // Wait for Processor to stop defer close(processorStopCh) // Tell Processor to stop // 内存突变检测,忽略 wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run) // 4. 运行用户自定义handler的处理逻辑 wg.StartWithChannel(processorStopCh, s.processor.run) defer func() { s.startedLock.Lock() defer s.startedLock.Unlock() s.stopped = true // Don't want any new listeners }() // 5.运行controller s.controller.Run(stopCh) }
3.1 NewDeltaFIFO
3.1.1 DeltaFIFO的定位
在apisever中的list-watch机制介绍中,就可以知道。直接使用list,watch api就可以获得全量和增量数据。
如果让我写一个最简单的client-go客户端,我实现的方式是:
(1)定义一个本地存储cache,list的时候将数据放到cache中
(2)然后watch的时候就更新cache数据,然后再将对象发送到自定义的add, update, del handler函数中
需要cache的原因:本地缓存一份etcd数据,这样控制器需要访问数据的话,直接从本地拿。
以上可以实现一个很简陋的客户端,但是还远远达不到informer机制的要求。
informer机制为啥需要DeltaFIFO?
(1)为啥需要FIFO队列?
很容易理解,FIFO是保障有序,不有序就会导致数据错乱。 队列是为了缓冲,如果更新的数据太多,informer机制可能就扛不住了
(2)为啥需要delta?
FIFO队列的元素总共就两个去向。第一用于同步本地缓存。第二用于发送给自定义的add, update, del handler函数。
假设某个极短的时间内,某一个对象做了大量的update,最后被删除了。这样的话,FIFO队列其实是堆积了很多数据。
一个一个发送给handler函数没有问题,因为用户就想知道这个过程。但是如果是一个一个的更新本地缓存,最后又delete了,那前面的update就浪费了。
所以这个时候DeltaFIFO队列出现了。它解决了这个问题。
3.1.2 DeltaFIFO结构介绍
DeltaFIFO可以认为是一个特殊的FIFO队列。Delta就是k8s