informer机制详解

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主要逻辑如下:

  1. new一个 deltafifo对象,并且指定对象的keyfun为 MetaNamespaceKeyFunc,就是用 ns/name 来当对象的key

  2. 生成config,利用config 生成一个controller

  3. 运行用户自定义handler的处理逻辑,s.processor.run (开启消费)

  4. 运行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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值