k8s client-go源码解析之informer 二
创始人
2024-05-30 22:56:25
0

Informer(二)

注意:本文内容为学习笔记,内容为个人见解,不保证准确性,但欢迎大家讨论何指教。

本篇介绍cache.SharedIndexInforme中 Controller及其组件
informer大致工作流程如下:
在这里插入图片描述

sharedIndexInformer

根据上一篇使用 Deployment 资源类型的informer创建举例, 我们可以定位到最终实现为:tools/cache/shared_informer.go

结构体定义如下:

type sharedIndexInformer struct {// 数据存储结构indexer    Indexer// 事件处理器controller Controller // 用于分发事件到EventHandlerprocessor             *sharedProcessor// 用于检查对象是否被篡改cacheMutationDetector MutationDetector // 用于获取资源数据listerWatcher ListerWatcher// 当前监听的资源类型objectType runtime.Object// 资源在被分发前进行的处理操作transform TransformFunc// 省略部分代码...
}

informer启动函数:

func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {// 初始化数据队列,用于资源数据的中转。fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{KnownObjects:          s.indexer,EmitDeltaTypeReplaced: true,})cfg := &Config{Queue:             fifo,ListerWatcher:     s.listerWatcher,ObjectType:        s.objectType,ObjectDescription: s.objectDescription,FullResyncPeriod:  s.resyncCheckPeriod,RetryOnError:      false,ShouldResync:      s.processor.shouldResync,Process:           s.HandleDeltas,WatchErrorHandler: s.watchErrorHandler,}// 省略部分代码...// ...// 启动数据突变检测器wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run)// 启动事件分发器wg.StartWithChannel(processorStopCh, s.processor.run)// 启动数据控制器s.controller.Run(stopCh)
}

SharedProcessor

SharedProcessor用于管理processorListener
processorListener监听OnAdd,OnUpdate,OnDelete这些动作,交由对应的注册的事件函数处理。
sharedProcessor的代码这里就不赘述了,着重看一下processorListener的实现。

type processorListener struct {// 事件首先会放到addCh中addCh  chan interface{}// addCh中的事件,会放到nextCh中,守护协程就可以读到事件了// 来不及处理的事件,会放到pendingNotifications这个环形队列中pendingNotifications buffer.RingGrowing// 事件最终会放到这个chan中nextCh chan interface{}// 注册的事件处理函数handler ResourceEventHandler// 省略部分代码...
}

pop函数用于将addCh中的事件取出,基本逻辑如下:
在这里插入图片描述

func (p *processorListener) pop() {defer utilruntime.HandleCrash()defer close(p.nextCh) var nextCh chan<- interface{}var notification interface{}for {select {// 将取出的事件放到nextCh中case nextCh <- notification:var ok bool// 读取缓冲区,缓冲区为空时将notification和nextCh置为nil// 这样就可以继续走下面的写入逻辑notification, ok = p.pendingNotifications.ReadOne()if !ok { // Nothing to popnextCh = nil // Disable this select case}case notificationToAdd, ok := <-p.addCh:if !ok {return}if notification == nil { // notification 为nil时,代表目前没有未处理的事件。// 这时候将nextCh,notification分别赋值,下一次循环就会将事件放到nextCh中notification = notificationToAddnextCh = p.nextCh} else {// notification不为nil时,代表有未处理完的事件,将事件放到环形队列中p.pendingNotifications.WriteOne(notificationToAdd)}}}
}

run方法从nextCh中消费

func (p *processorListener) run() {stopCh := make(chan struct{})wait.Until(func() {for next := range p.nextCh {switch notification := next.(type) {case updateNotification:p.handler.OnUpdate(notification.oldObj, notification.newObj)// 省略部分代码...}close(stopCh)}, 1*time.Second, stopCh)
}

controller

controllersharedIndexInformer的核心。
它负责从listerWatcher获取资源数据,然后将数据存储到indexer中,同时将数据分发到processor中。
我们来看看controller的定义:

type controller struct {config         Config// 用于获取数据,并转换成目标资源对象reflector      *ReflectorreflectorMutex sync.RWMutexclock          clock.Clock
}

controller的启动函数:

func (c *controller) Run(stopCh <-chan struct{}) {// 省略部分代码...r := NewReflectorWithOptions(c.config.ListerWatcher,c.config.ObjectType,c.config.Queue,ReflectorOptions{ResyncPeriod:    c.config.FullResyncPeriod,TypeDescription: c.config.ObjectDescription,Clock:           c.clock,},)// 省略部分代码...c.reflectorMutex.Lock()c.reflector = rc.reflectorMutex.Unlock()var wg wait.Group// 启动反射器,获取数据wg.StartWithChannel(stopCh, r.Run)// 启动processLoop,操作数据wait.Until(c.processLoop, time.Second, stopCh)wg.Wait()
}

NewReflectorWithOptions会根据配置创建对应的Reflector对象。
Reflector.Run会启动一个ListAndWatch的goroutine,用于获取资源数据。
ListAndWatch会携带最后一次的资源版本号,加上重试机制来保证不会丢失数据。
然后将数据存储到DeltaFIFO中。

func (r *Reflector) Run(stopCh <-chan struct{}) {// 省略部分代码...wait.BackoffUntil(func() {if err := r.ListAndWatch(stopCh); err != nil {r.watchErrorHandler(r, err)}}, r.backoffManager, true, stopCh)
}
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {// 首先list获取全量数据 err := r.list(stopCh)if err != nil {return err}// 省略部分代码...// 请求APIServer的参数options := metav1.ListOptions{// 指定了资源版本,这样就可以获取到资源版本之后的数据ResourceVersion: r.LastSyncResourceVersion(),TimeoutSeconds:} &timeoutSeconds,AllowWatchBookmarks: true,}// 重试器,仅限于调用apiServer的网络错误,比如超时、连接中断等retry := NewRetryWithDeadline(r.MaxInternalErrorRetryDuration, time.Minute, apierrors.IsInternalError, r.clock)for {// 省略部分代码...// 数据操作函数err = watchHandler(start, w, r.store, r.expectedType, r.expectedGVK, r.name, r.typeDescription, r.setLastSyncResourceVersion, r.clock, resyncerrc, stopCh)retry.After(err)if err != nil {if err != errorStopRequested {switch {// 省略部分代码...case apierrors.IsTooManyRequests(err):<-r.initConnBackoffManager.Backoff().C()continue}return nil}}
}

watchHandler

func watchHandler() error {
// 省略部分代码...
loop:for {select {case <-stopCh:return errorStopRequestedcase err := <-errc:return errcase event, ok := <-w.ResultChan():if !ok {// 这里不清楚为什么要用这种方式// 可能是历史问题,这部分的代码比较老,是2016年的break loop}// 一堆数据合法性判断,省略部分代码...resourceVersion := meta.GetResourceVersion()switch event.Type {case watch.Added:// 写入到DeltaFIFOerr := store.Add(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", name, event.Object, err))}case watch.Modified:// 省略部分代码...case watch.Deleted:// 省略部分代码...case watch.Bookmark:default:utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", name, event))}// 更新记录的版本号,请求的时候会用到setLastSyncResourceVersion(resourceVersion)if rvu, ok := store.(ResourceVersionUpdater); ok {rvu.UpdateResourceVersion(resourceVersion)}eventCount++}}// 省略部分代码...
}

processLoop

processLoop会从DeltaFIFO中获取数据,然后将数据存储到indexer中,同时将数据分发到processor中。

func (c *controller) processLoop() {for {// 函数是在config中传入的obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))// 省略部分代码...}
}
// 最终定位到的函数
func processDeltas(handler ResourceEventHandler,clientState Store, transformer TransformFunc,deltas Deltas,isInInitialList bool,
) error {// 代码里面有多处用Store关键字的地方// 命名也有很多store// 不同的位置,代表的含义也不一样,这里需要注意for _, d := range deltas {obj := d.Object// transformer用于在处理数据之前对数据进行转换// 默认transformer是nilif transformer != nil {var err errorobj, err = transformer(obj)if err != nil {return err}}switch d.Type {case Sync, Replaced, Added, Updated:// 省略部分代码...// 写入到indexer中,即最终的数据存储位置if err := clientState.Update(obj); err != nil {return err}// 执行事件处理函数handler.OnUpdate(old, obj)case Deleted:// 省略部分代码...}}return nil
}	

总结

informercontroller负责数据控制,包括:获取数据、数据处理、数据分发。
controller中由:

  • Reflector负责获取数据并写入到队列。
    数据获取时会携带上一次的版本号,这样就可以获取到版本号之后的数据。
  • processLoop取出数据,写入到indexer中,同时将数据分发到processor中。processor中会根据动作类型,OnAddOnUpdateOnDelete,执行对应的事件处理函数。

相关内容

热门资讯

中证A500ETF摩根(560... 8月22日,截止午间收盘,中证A500ETF摩根(560530)涨1.19%,报1.106元,成交额...
A500ETF易方达(1593... 8月22日,截止午间收盘,A500ETF易方达(159361)涨1.28%,报1.104元,成交额1...
何小鹏斥资约2.5亿港元增持小... 每经记者|孙磊    每经编辑|裴健如 8月21日晚间,小鹏汽车发布公告称,公司联...
中证500ETF基金(1593... 8月22日,截止午间收盘,中证500ETF基金(159337)涨0.94%,报1.509元,成交额2...
中证A500ETF华安(159... 8月22日,截止午间收盘,中证A500ETF华安(159359)涨1.15%,报1.139元,成交额...
科创AIETF(588790)... 8月22日,截止午间收盘,科创AIETF(588790)涨4.83%,报0.760元,成交额6.98...
创业板50ETF嘉实(1593... 8月22日,截止午间收盘,创业板50ETF嘉实(159373)涨2.61%,报1.296元,成交额1...
港股异动丨航空股大幅走低 中国... 港股航空股大幅下跌,其中,中国国航跌近7%表现最弱,中国东方航空跌近5%,中国南方航空跌超3%,美兰...
电网设备ETF(159326)... 8月22日,截止午间收盘,电网设备ETF(159326)跌0.25%,报1.198元,成交额409....
红利ETF国企(530880)... 8月22日,截止午间收盘,红利ETF国企(530880)跌0.67%,报1.034元,成交额29.0...