深入源码分析kubernetes informer机制(四)DeltaFIFO


[阅读指南]
这是该系列第四篇
基于kubernetes 1.27 stage版本
为了方便阅读,后续所有代码均省略了错误处理及与关注逻辑无关的部分。


文章目录

  • client-go中的存储结构
  • DeltaFIFO
    • delta
    • 索引 key
    • queue push操作
      • delta push 去重
    • queue pop操作
  • 总结

client-go中的存储结构

如下图,clinet-go中定义了存储类型接口store,用来提供存储对象的基本能力。

queue继承了store接口,并提供了队列的能力,队列中可以保存需要增删改的存储对象的key,它会取出队头元素,调用PopProcessFunc处理。

queue的实现有两个:FIFOdeltaFIFO

deltaFIFO的不同点在于,deltaFIFO队列中,key对应的不是对象本身,而是对象的delta。

另外deltaFIFO除了通过add、update、delete添加元素,还有两种特殊的方式:replaced和sync。replaced一般发生在资源版本更新时,而sync由resync定时发起。

DeltaFIFO

下面是deltaFIFO数据结构的定义

type DeltaFIFO struct {// 并发读写锁lock sync.RWMutexcond sync.Cond// `items` maps a key to a Deltas.// 资源对象的key与对应的delta数组,每个数组至少都会有一个deltaitems map[string]Deltas// 按照FIFO队列顺序存储key,用来给pop()消费。// 该数组不会有重复值,并且所有元素都一定在items中queue []string// 生成key值的函数,默认是 MetaNamespaceKeyFunckeyFunc KeyFunc// 本地缓存中已知的所有资源对象的keyknownObjects KeyListerGetter......
}

delta

如前面所说,deltaFIFO中key映射的不是对象本身,是delta数组。

根据Delta数据结构的定义,delta包含了一个资源对象的变更类型及变更的内容。这里的Object不一定是完整的资源数据,大部分场景下只会有变更的部分信息。

type Delta struct {Type   DeltaTypeObject interface{}
}type DeltaType string
const (Added   DeltaType = "Added"Updated DeltaType = "Updated"Deleted DeltaType = "Deleted"Replaced DeltaType = "Replaced"Sync DeltaType = "Sync"
)

举个栗子,本地已经有了一个pod对象,

&Pod{Name:      "mypod",Namespace: "default",Labels:    map[string]string{"app": "web", "version": "0.0.1"},
}

此时mypod的 lable从web变成了app-server,reflector就会创建一个这样的delta对象放入FIFO队列中。

&Delta{Type:   "Updated",Object: &Pod{Name:      "mypod",Namespace: "default",Labels:    map[string]string{"app": "app-server"},},
}

索引 key

deltaFIFO队列中,存储的是delta的key值,通过key值可以在items map中获取到对应的delta对象。

这个key值在初始化FIFO时通过KeyFunction进行定义,使用者没有指定时,都会使用自带的命名函数 MetaNamespaceKeyFunc 进行命名,命名规则是

  • namespace不为空,key为/
  • namespace为空,key为

这里的name是在yaml资源配置中的matadata.name,比如上面的mypod。在同一个资源下,name在所有api version都一定是唯一的。

func MetaNamespaceKeyFunc(obj interface{}) (string, error) {if key, ok := obj.(ExplicitKey); ok {return string(key), nil}meta, err := meta.Accessor(obj)if len(meta.GetNamespace()) > 0 {return meta.GetNamespace() + "/" + meta.GetName(), nil}return meta.GetName(), nil
}

queue push操作

watcher监控的资源变更时,会调用deltaFIFO中Added、Updated、Deleted、Replaced、Sync方法,最终它们都会通过queueActionLocked 方法往deltaFIFO队列中加入对应类型的delta对象。

queueActionLocked 也就是deltaFIFO的入队操作。

和一般的入队不同的是,新加入的delta不是直接加入到队尾,队列queue数组中保存的是delta的key。所以入队的操作是这样的

  1. 获取delta对应的key值(还记得keyfunc吗,又是它)
  2. 如果delta所属的资源key已经在队列中,直接将delta添加到key对应到deltas数组末尾。更新已存在的资源delta并不会影响他的key在队列中的位置。
  3. 如果delta所属的资源key不在队列中,就将key添加到队列末尾,并在items中关联key和delta
func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {id, err := f.KeyOf(obj)// 自定义的转换函数。可以在delta事件被处理之前完成一些预处理// 常见的用法是用来过滤一些处理程序不关注的资源对象、以及处理数据格式等if f.transformer != nil {obj, err = f.transformer(obj)}// 将新的delta放入资源key对应的delta数组末尾// 如果原本的key不存在,就是创建了一个新的数组,并将新的delta放入其中oldDeltas := f.items[id]newDeltas := append(oldDeltas, Delta{actionType, obj})// 对delta数组中的delta去重newDeltas = dedupDeltas(newDeltas)// 判断key是否已经在队列中,并且更新key对应的delta数组if len(newDeltas) > 0 {if _, exists := f.items[id]; !exists {f.queue = append(f.queue, id)}f.items[id] = newDeltasf.cond.Broadcast()}return nil
}

delta push 去重

上一节提到,delta进行push操作时,会对加入的delta进行去重。去重逻辑目前只针对两个delete类型的delta有效:当delta数组中倒数第一个和第二个delta都是delete类型时,将会去掉其中一个

func dedupDeltas(deltas Deltas) Deltas {n := len(deltas)if n < 2 {return deltas}a := &deltas[n-1]b := &deltas[n-2]if out := isDup(a, b); out != nil {deltas[n-2] = *outreturn deltas[:n-1]}return deltas
}// 判断a、b两个delta是否重复
// 目前暂时只有两个delete类型的delta会被判定为重复。
func isDup(a, b *Delta) *Delta {if out := isDeletionDup(a, b); out != nil {return out}return nil
}// 判定两个delta是否都是deleted类型
func isDeletionDup(a, b *Delta) *Delta {if b.Type != Deleted || a.Type != Deleted {return nil}if _, ok := b.Object.(DeletedFinalStateUnknown); ok {return a}return b
}

举个小小的例子来回顾一下delta push操作。假设queue中有3个pod对象,对应了不同的变更事件,如下所示。

此时watcher监听到资源发生变化:

  1. pod2收到了updated事件
  2. pod1收到了deleted事件
  3. pod3收到了deleted事件

于是,三个delta入队成功后的队列图如下

pod1已有一个deleted事件,再次收到deleted后,经过dedupDeltas去重,最终只保留一个deleted。

pod3虽然有两个deleted事件,但是他们并不是连续的事件,不会被去重

queue pop操作

deltaFIFO出队的操作和普通的队列出队类似,从队头取出一个资源对象key,并删除items中key对应的deltas数组。

pop出队时,会调用传参PopProcessFunc对出队元素进行处理。

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()}// 取出队首的资源对象keyid := f.queue[0]f.queue = f.queue[1:]// 获取key对应的deltas数组item, ok := f.items[id]// 执行pop处理函数,处理delta事件,如果处理失败了,资源对象会被重新加入到队列中。// 但是如果队列中存在相同的对象,资源对象会被丢弃。err := process(item, isInInitialList)if e, ok := err.(ErrRequeue); ok {f.addIfNotPresent(id, item)err = e.Err}return item, err}
}

这里一开始有个小疑问,如果资源的delta处理失败了,并且队列中又出现了同样的资源key,这部分delta数据不就丢失了吗?

但是仔细看出队入队公用一个锁,pop处理对象时不会有新的对象入队,所以理论上不会出现在addIfNotPresent时,key是persent的情况。而deltaFIFO入队的逻辑,也不会存在一个队列有两个相同的key的情况,所以不会有丢失的问题,addIfNotPresent应该只是加多一层保障。如果理解有问题,欢迎大佬们指正。

回顾一下pop的调用方processLoop,调用pop时传入PopProcessFunc(c.config.Process))。

系列第一篇介绍informer时提到过,c.config.Process最终调用的是processDeltas函数,它包含了数据同步到存储,以及调用注册的用户函数两个操作。

func (c *controller) processLoop() {for {obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))if err != nil {...}}
}// 数据处理函数
func processDeltas(handler ResourceEventHandler,clientState Store,deltas Deltas,isInInitialList bool,
) error {// from oldest to newestfor _, d := range deltas {obj := d.Object// 区分事件类型进行处理switch d.Type {case Sync, Replaced, Added, Updated:// 同步存储数据if old, exists, err := clientState.Get(obj); err == nil && exists {if err := clientState.Update(obj); err != nil {return err}// 回调用户函数handler.OnUpdate(old, obj)} else {// 同步存储数据if err := clientState.Add(obj); err != nil {return err}// 回调用户函数handler.OnAdd(obj, isInInitialList)}case Deleted:// 同步存储数据if err := clientState.Delete(obj); err != nil {return err}// 回调用户函数handler.OnDelete(obj)}}return nil
}

总结

还是用上一节的例子,小结回顾一下整体的流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/68290.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows 编译CEF源码详细记录

背景 默认的CEF不支持音视频功能&#xff0c;需要下载源码将ffmpeg开关打开&#xff0c;再进行编译。 Linux编译参考&#xff1a; 《Linux CEF源码下载编译详细记录》 创建目录结构 code/automate/automate-git.py <-- CEF build scriptchromium_git/cef/ …

说一下什么是tcp的2MSL,为什么客户端在 TIME-WAIT 状态必须等待 2MSL 的时间?

1.TCP之2MSL 1.1 MSL MSL:Maximum Segment Lifetime报文段最大生存时间&#xff0c;它是任何报文段被丢弃前在网络内的最长时间 1.2为什么存在MSL TCP报文段以IP数据报在网络内传输&#xff0c;而IP数据报则有限制其生存时间的TTL字段&#xff0c;并且TTL的限制是基于跳数 1.3…

数据安全服务能力评定资格证书-申请流程

数据安全服务能力评定&#xff08;以下简称能力评定&#xff09;是指对数据安全服务提供商从事数据安全服务综合能力的评定&#xff0c;包括技术能力、服务能力、质量保证能力、人员构成与素质、经营业绩、资产状况等要素。 用于对中华人民共和国境内的数据安全服务提供商提供…

MySQL和Redis如何保证数据一致性

MySQL与Redis都是常用的数据存储和缓存系统。为了提高应用程序的性能和可伸缩性&#xff0c;很多应用程序将MySQL和Redis一起使用&#xff0c;其中MySQL作为主要的持久存储&#xff0c;而Redis作为主要的缓存。在这种情况下&#xff0c;应用程序需要确保MySQL和Redis中的数据是…

Stable Diffusion+Roop单张图片换头roop安装配置【包含roop破解方法】

1.首先安装秋叶大佬的webui 2.然后在拓展里面搜索roop,下载roop插件,然后重启webui 3.重启后,在文生图和图生图的界面,就可以看到roop的入口 4.这里面,需要提前安装Visual Studio. 勾选一些必要的选项,这里可以参照b站的视频 # 秋叶版本Stablediffusion的Roop插件的安装 …

15个免费的图标素材网站推荐

想象力没有限制。你在寻找下载免费矢量图标网站吗&#xff1f;本文为需要使用各种图标美化网页布局和界面设计&#xff0c;简单提升更好用户体验的设计师提供了15个矢量图标网站。 1.即时设计资源社区 即时设计是国内一款专业级的 UI 设计工具&#xff0c;像 PC 端的网页&…

【论文阅读】基于深度学习的时序预测——LTSF-Linear

系列文章链接 论文一&#xff1a;2020 Informer&#xff1a;长时序数据预测 论文二&#xff1a;2021 Autoformer&#xff1a;长序列数据预测 论文三&#xff1a;2022 FEDformer&#xff1a;长序列数据预测 论文四&#xff1a;2022 Non-Stationary Transformers&#xff1a;非平…

【独立版】新零售社区团购电商系统生鲜水果商城兴盛优选十荟团源码

【独立版】新零售社区团购电商系统生鲜水果商城兴盛优选十荟团源码

[MAUI]在.NET MAUI中实现可拖拽排序列表

.NET MAUI 中提供了拖放(drag-drop)手势识别器&#xff0c;允许用户通过拖动手势来移动控件。在这篇文章中&#xff0c;我们将学习如何使用拖放手势识别器来实现可拖拽排序列表。在本例中&#xff0c;列表中显示不同大小的磁贴&#xff08;Tile&#xff09;并且可以拖拽排序。 …

基于鲲鹏平台Ceph深度性能调优

刘亮奇 架构师技术联盟 2021-04-12 07:50 摘自&#xff1a; https://mp.weixin.qq.com/s/o9HH-8TF0DbMqHrvsFh1NA 随着 IOT、大数据、移动互联等应用的暴涨&#xff0c;产生的数据也越来越多&#xff0c;整个存储市场总量也逐年增长&#xff0c;预计到 2021 年分布式存储会占到…

企业权限管理(十六)-aop日志

SysLog实体类 public class SysLog {private String id;private Date visitTime;private String visitTimeStr;private String username;private String ip;private String url;private Long executionTime;private String method;public String getId() {return id;}public v…

CDH6.3.2搭建HIVE ON TEZ

参考 https://blog.csdn.net/ly8951677/article/details/124152987 ----配置hive运行引擎 在/etc/hive/conf/hive-site.xml中修改如下&#xff1a; hive.execution.engine mr–>tez hive.execution.engine 设为tez或者运行代码的时候&#xff1a; set hive.execution.eng…