client-go源码分析(2.2):DeltaFIFO

前言

本文聚焦client-go v11.0.0 controller框架DeltaFIFO对象,分析源码理解DeltaFIFO的实现。DeltaFIFO是client-go controller framework的重要环节,它的作用是保证Reflector和Indexer之间对象同步。可以说,DeltaFIFO是连接生产者(Reflector)和消费者(Indexer)的通道。阅读本文前希望读者已阅读《client-go源码分析》前面的系列文章,要求已了解Informer和Reflector。

1 DeltaFIFO介绍

DeltaFIFO构造函数

以简单的示意图描述DeltaFIFO的结构:

DeltaFIFO核心结构示意图

DeltaFIFO的核心结构包括items和queue,对应Delta和FIFO。

1.1 Delta

Delta顾名思义是变化和差异,因此items的作用是缓存某个对象的一系列变更行为(Deltas)。正因为这里保存的是Delta(当前对象和前一个对象的差异),有序地保存一系列行为才有意义,单独的某个Updated状态无法正确同步对象。

items是map结构,key是由keyfunc计算出来的对象key;value是deltas,即delta列表。delta数据结构如下:

Delta数据结构

当前版本DeltaType的值只有Added, Updated, Deleted, Sync;Object即runtime.Object。

1.2 FIFO

FIFO即先进先出队列,这里queue仅把对象键(objkey)入列。消费者通过objkey查找items的Deltas并依次同步所有行为。queue是对象同步处理队列,消费者仅处理queue中存在的objkey。

2 DeltaFIFO基本功能

上述已经提到DeltaType只有四种:Added, Updated, Deleted, Sync。这里仅分析最难理解的Sync函数,Sync是List机制和Resync触发的全量同步;Added,Updated,Deleted是Watch触发的增量同步。

2.1 深入理解Sync:Replace()

Sync是比较特殊的处理,理解了Sync也就不难理解其他行为。Sync是指Reflector全量同步Indexer,从DelaFIFO的实现看是调用Replace函数“重建”Indexer。DelaFIFO的Replace实现了Sync行为(Resync也调用该函数)。controller第一次启动时,Reflector的List机制触发DeltaFIFO的Sync行为,《Client-Go源码分析(2.1):Reflector》会提到r.syncWith(items,

resourceVersion)函数,该函数调用

func (f *DeltaFIFO) Replace(list []interface{},

resourceVersion string) error

开始分析Replace代码前,先看一个重要的公共函数queueActionLocked,DeltaFIFO的所有行为都会调用它。queueActionLocked的功能是:仅处理某个动作。若处理一系列动作或多个对象,需要外部调用者重复调用它。

queueActionLocked注释
Replace注释1
Replace注释2
Replace注释3

2.2 queueActionLocked的特殊处理分析

上文提到的queueActionLocked特殊处理针对一种场景:

obj的id是objkey1,该对象最后一次动作是Deleted,并且objkey1仍在queue中等待处理。此时周期性被动触发一次Resync(会调用Replace)。k8s集群真实的runtime.Object已删除,但Indexer的同步尚未完成,Indexer缓存里仍有这个对象。

sync场景分析

假设queueActionLocked更新了deltas如上图(左)所示,该对象会先删除,然后添加回来!从Pop()函数里的回调函数Process可以看到这个过程。前几篇文章已经提到过,Process的入参obj是Deltas队列,该函数依次处理每个Delta行为。当Process处理到上图(左)Deleted时Index会删除对象;当它处理到最后的Sync时,Index又会添加这个对象。因此,queueActionLocked通过特殊处理规避了这个问题,现有代码的处理方式如上图(右)所示,直接返回不更新deltas。

Process的实现

2.3 sync场景的DeleteFinalStateUnknown分析

// DeletedFinalStateUnknown isplaced into a DeltaFIFO in the case where

// an object was deleted but thewatch deletion event was missed. In this

// case we don't know the final"resting" state of the object, so there's

// a chance the included `Obj` isstale.

这个对象困惑了我许久,从注释上看是针对Watch Deleted事件丢失,导致k8s实际对象和Indexer缓存不一致的场景。我们用图展示两种场景:1. Deleted事件未丢失,只是queue待处理该对象;2. Deleted事件丢失。

Deleted消息是否丢失场景比较

上图(左)是Deleted消息丢失的代码处理,queueActionLocked会添加DeleteFinalStateUnknown对象到deltas,并保存到Indexer。DeleteFinalStateUnknown是对runtime.Object的封装。

上图(右)是Deleted消息未丢失的代码处理。deltas对象变化过程如下:

deltas对象变化过程

从Process回调函数看,是直接调用clientState.Delete(d.Object)删除对象的,Indexer计算对象键是否会异常呢?抱着好奇心看了一眼keyFunc(计算对象键的函数,下一篇文章会讲到)DeletionHandlingMetaNamespaceKeyFunc:

DeletionHandlingMetaNamespaceKeyFunc的实现

DeletionHandlingMetaNamespaceKeyFunc果然有对DeletedFinalStateUnknown的处理(obj.(DeletedFinalStateUnknown)是类型断言用法),因此不需要担心获取对象键会异常。如果是我们自己实现keyFunc,一样要考虑到DeletedFinalStateUnknown这个而特殊对象。

到此为止DeltaFIFO的主要机制已分析完毕。

3 结语

DeltaFIFO的部分代码比较晦涩,尤其是特殊处理增加了阅读难度。特殊处理一时爽,代码维护两行泪。

4 参考文档

https://blog.csdn.net/li_101357/article/details/89763992

推荐阅读更多精彩内容