深入源码分析kubernetes informer机制(二)Reflector


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


文章目录

  • Reflector是什么
  • 整体结构
  • 工作流程
    • list拉取数据
    • 缓存resync操作
    • watch监听操作
  • 总结

Reflector是什么

reflector在informer中就像是一个对外的窗口,它与api-server建立连接,监听和获取来自api-server的资源变化信息,并把这些信息放进deltaFIFO中,交给下一个环节处理。

整体结构

与api-server进行交互,通过list获取指定的全量资源,watch监听指定的资源变化事件,并将这些事件放入delta FIFO队列中。
结构与交互如下图

// 省略了部分字段,只留下我们关注的
type Reflector struct {// name identifies this reflector. By default it will be a file:line if possible.name string// reflector对象需要监控的资源类型,比如上一节workqueue中的&v1.Pod{}expectedType reflect.Type// deltaFIFO 队列存储对象store Store// 实现list/watchlisterWatcher ListerWatcher// 上次更新的资源版本号,用来判断当前的node的资源状况lastSyncResourceVersion string......
}

工作流程

reflecter主函数比较简单,循环同步运行ListAndWatch直到收到stop信号。

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)
}

ListAndWatch主要做了这几件事:

  1. 通过stream或者chunk方式拉取全量list数据
  2. 开启一个协程进行缓存resync操作。
  3. 循环执行watch监听操作
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {...fallbackToList := !r.UseWatchList// stream式同步if r.UseWatchList {w, err = r.watchList(stopCh)...if err != nil {...fallbackToList = truew = nil}}// chunk式同步if fallbackToList {err = r.list(stopCh)if err != nil {return err}}...go r.startResync(stopCh, cancelCh, resyncerrc)return r.watch(w, stopCh, resyncerrc)
}

接下来咱一步步来看。

list拉取数据

ListAndWatch拉取全量数据时,出现了两种数据拉取的方式,list /watchstream list /watch

stream list是 kubernetes 1.27 引入的新方案,通过 ENABLE_CLIENT_GO_WATCH_LIST_ALPHA 变量可以启用stream list,默认会使用原有的list/watch。后续会单独开一篇介绍stream list方案,详情可以通过KEP-3157了解

前者在初始化时list拉取全量数据,通过watch更新增量变化。
后者可以通过watch 请求的方式获取list数据,从而减轻大规模集群初始化list数据时的资源消耗。
在建立watch连接时,携带如下两个参数即可告知服务器使用streaming list进行一致性读取。
sendInitialEvents=true
resourceVersionMatch=NotOlderThan

常规的list流程借用这个博主画的时序图来看下。
在这里插入图片描述

缓存resync操作

resync负责定期将本地的缓存重新加入deltaFIFO队列,确保本地缓存与controller的数据一致性。

国内太多博客没了解清楚就介绍这一部分是与api-server交互,进行relist。实际上resync完全没有涉及到服务端的部分,他就是一个本地缓存的同步机制。与服务端的交互使用list/watch已经完全可以确保资源一致性了,基本不怎么需要进行relist操作,并且对于节点非常多的大集群来说,list非常消耗资源,何况是定期relist呢。

关于resync机制的介绍,不在这里展开,详细看下一篇笔记。

watch监听操作

watch的实现非常巧妙,它利用了http的chunk编码传输机制建立长连接,来实现动态的数据监听,可以了解分块传输编码。
同样借用一张时序图来看下watch的流程
在这里插入图片描述

reflector通过Watcher监听api-server端的数据delta事件,并将这些事件放入deltaFIFO中统一处理。

// 在这里向服务端发起watch请求,并接收和处理资源变更事件
func (r *Reflector) watch(w watch.Interface, stopCh <-chan struct{}, resyncerrc chan error) error {...for {...// w == nil表示使用常规的list/watch方式,streaming 方式会创建特殊的watcherif w == nil {timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))options := metav1.ListOptions{// 上次同步的资源版本,也就是本地的资源版本。以此来获取增量的数据ResourceVersion: r.LastSyncResourceVersion(),// watch 超时时间,长时间没有接受任务事件的watcher会被关掉,避免长时间挂起。TimeoutSeconds: &timeoutSeconds,// watch书签,避免watch重启时请求api-server导致的消耗。AllowWatchBookmarks: true,}// 创建一个watch对象,监听api-server的资源变更事件,将接收到的事件丢进resultChan中w, err = r.listerWatcher.Watch(options)...}// 将resultChan中的取出放入FIFO 队列err = watchHandler(start, w, r.store, r.expectedType, r.expectedGVK, r.name, r.typeDescription, r.setLastSyncResourceVersion, nil, r.clock, resyncerrc, stopCh)// 失败重试逻辑...}
}

建立连接的逻辑在这一行
w, err = r.listerWatcher.Watch(options)

还是用上一篇workqueue来看看这个Watch实例的实现。
从Watch函数一路往上追溯,可以看到先是与server建立了http连接,再通过watch标记建立了watch连接,创建stream watcher对象,并拉起一个协程去处理监听到的事件信息。

  • 此后所有监听的delta事件都会经过receive协程进入到resultChan中。
// reflector调用的watch函数
func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) {return lw.WatchFunc(options)
}// watchFunc函数的定义
func NewFilteredListWatchFromClient(c Getter, resource string, namespace string, optionsModifier func(options *metav1.ListOptions)) *ListWatch {...watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {options.Watch = true // 向服务端请求chunk连接optionsModifier(&options)return c.Get().Namespace(namespace).Resource(resource).VersionedParams(&options, metav1.ParameterCodec).Watch(context.TODO()) // 这里调用了getter的watch函数// getter是controller初始化时建立的http客户端: clientset.CoreV1().RESTClient()}return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
}func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {...	url := r.URL().String()for {req, err := r.newHTTPRequest(ctx)resp, err := client.Do(req)if err == nil && resp.StatusCode == http.StatusOK {return r.newStreamWatcher(resp)}...}
}func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher {sw := &StreamWatcher{source:   d,reporter: r,result: make(chan Event),done: make(chan struct{}),}go sw.receive() // 处理消息事件的协程return sw
}// 解析接收到的事件,并放到resultChan中等待后续处理。
func (sw *StreamWatcher) receive() {for {// 解析数据action, obj, err := sw.source.Decode()select {case <-sw.done:return// 将事件发送到resultChancase sw.result <- Event{Type:   action,Object: obj,}:}}
}
  • 进入resultChan的事件,由watchHandler取出再分类添加到FIFO队列中。
func watchHandler(start time.Time,w watch.Interface,	// watch实例store Store, // 存储对象 比如delta FIFO queue...
) error {...
loop:for {select {case <-stopCh:return errorStopRequestedcase err := <-errc:return err// 从ResultChan中取出变更事件,并放进队列中,比如delta FIFO队列中case event, ok := <-w.ResultChan():// 省略了一些资源过滤和错误处理...// 解析监听到的事件数据meta, err := meta.Accessor(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", name, event))continue}// 解析资源事件的版本resourceVersion := meta.GetResourceVersion()switch event.Type {case watch.Added: err := store.Add(event.Object) // 往队列添加add delta事件... // err handlecase watch.Modified: err := store.Update(event.Object) // 往队列添加update delta事件... // err handlecase watch.Deleted:	err := store.Delete(event.Object) // 往队列添加delete delta事件,在此之前会判断事件对应的资源对象是否存在... // err handlecase watch.Bookmark:...default:... // err handle}// 更新resourceVersion版本号,下一轮watch就不会再收到重复的更新事件setLastSyncResourceVersion(resourceVersion)if rvu, ok := store.(ResourceVersionUpdater); ok {rvu.UpdateResourceVersion(resourceVersion)}...}}...return nil
}

总结

用一个图来回顾下reflector各个模块的关系~
在这里插入图片描述

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

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

相关文章

nginx部署时http接口正常,ws接口404

可以这么配置 map $http_upgrade $connection_upgrade {default upgrade; close; }upstream wsbackend{server ip1:port1;server ip2:port2;keepalive 1000; }server {listen 20038;location /{ proxy_http_version 1.1;proxy_pass http://wsbackend;proxy_redirect off;proxy…

Android-网络访问技术Retrofit浅析

Retrofit是一种基于注解的网络请求库&#xff0c;专门用于在Android应用中进行网络访问。它使用简洁的方式定义了网络请求的接口&#xff0c;并自动将请求结果解析为Java对象。Retrofit的核心原理是利用了Java的动态代理技术&#xff0c;将网络请求接口的注解信息转化为具体的网…

软件压力测试对软件产品起到什么作用?

一、软件压力测试是什么? 软件压力测试是一种通过模拟正常使用环境中可能出现的大量用户和大数据量的情况&#xff0c;来评估软件系统在压力下的稳定性和性能表现的测试方法。在软件开发过程中&#xff0c;经常会遇到一些性能瓶颈和稳定性问题&#xff0c;而软件压力测试的作…

星星之火:国产讯飞星火大模型的实际使用体验(与GPT对比)

#AIGC技术内容创作征文&#xff5c;全网寻找AI创作者&#xff0c;快来释放你的创作潜能吧&#xff01;# 文章目录 1 前言2 测试详情2.1 文案写作2.2 知识写作2.3 阅读理解2.4 语意测试&#xff08;重点关注&#xff09;2.5 常识性测试&#xff08;重点关注&#xff09;2.6 代码…

在 OpenCV 中使用深度学习进行年龄检测-附源码

文末附完整源码和模型文件下载链接 在本教程中,我们将了解使用 OpenCV 创建年龄预测器和性别分类器项目的整个过程。 年龄检测 我们的目标是创建一个程序,使用图像来预测人的性别和年龄。但预测年龄可能并不像你想象的那么简单,为什么呢?您可能会认为年龄预测是一个回归问…

炫酷UI前端效果的CSS生成工具

提升设计人员和前端开发人员的工作 推荐炫酷UI前端效果的CSS生成工具1.Neumorphism2.带有渐变的图标3.Interactions4.大型数据库5.动画6.Mask7.动画按钮8. 自定义形状分隔线9.背景图案10. SVG波浪推荐炫酷UI前端效果的CSS生成工具 1.Neumorphism 地址:https://neumorphism.i…

模拟实现定时器

前言 定时器的功能&#xff0c;就是一个类似于闹钟的到点运行的功能。 目录 前言 一、Timer 类 二、分析 Timer 类 三、完整代码实现 结语 一、Timer 类 Java库提供 Timer 类&#xff0c;实现 schedule 方法&#xff0c;给方法提供任务和时间&#xff0c;到时间就运行任务…

Java:PO、VO、BO、DO、DAO、DTO、POJO

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Java&#xff1a;PO、VO、BO、DO、DAO、DTO、POJO PO持久化对象&#xff08;Persistent Object&#xff09; PO是持久化对象&#xff0c;用于表示数据库中的实体或表…

第六阶|见道明心的笔墨(上)从书法之美到生活之美——林曦老师的线上直播书法课

如果你有需要&#xff0c;可以找我的&#xff0c;我这边有老师的所有课程 如果你有需要&#xff0c;可以找我的&#xff0c;我这边有老师的所有课程

Linux学习之初识Linux

目录 一.Linux的发展历史及概念 1.什么是Linux UNIX发展的历史&#xff1a; Linux发展历史&#xff1a; 2. 开源 商业化发行版本 二. 如何搭建Linux环境 Linux 环境的搭建方式主要有三种&#xff1a; 1. 直接安装在物理机上 2. 使用虚拟机软件 3. 使用云服务器 三. …

设计模式十七:迭代器模式(Iterator Pattern)

迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;它提供了一种访问聚合对象&#xff08;例如列表、集合、数组等&#xff09;中各个元素的方法&#xff0c;而无需暴露其内部表示。迭代器模式将遍历元素和访问元素的责任分离开来&#xff0…

无涯教程-Perl - setservent函数

描述 在第一次调用getservent之前,应先调用此函数。 STAYOPEN参数是可选的,在大多数系统上未使用。当getservent()检索服务数据库中下一行的信息时,然后setervent设置(或重置)枚举到主机条目集的开头。 语法 以下是此函数的简单语法- setservent STAYOPEN返回值 此函数不返…