kubernetes-informer机制
一、概念
informer 是 client-go 中的核心工具包,在kubernetes中,各个组件通过HTTP协议跟 API Server 进行通信。如果各组件每次都直接和API Server 进行交互,会给API Server 和ETCD造成非常大的压力。在不依赖任何中间件的情况下,通过informer保证了消息的实时性、可靠性和顺序性。
二、架构设计
informer运行原理
三、源码分析
3.1 informer启动
informer启动有以下步骤:
- 注册及启动processLoop和reflector
- reflector开始LIST和WATCH,watch到的数据进行对比处理,存入到queue中
- processLoop开始循环pop队列数据
factory := informers.NewSharedInformerFactory(clientset, 0)podInformer := factory.Core().V1().Pods().Informer()podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: func(obj interface{}) {mObj := obj.(v1.Object)log.Printf("New pod added: %s", mObj.GetName())},UpdateFunc: func(oldObj, newObj interface{}) {oObj := oldObj.(v1.Object)nObj := newObj.(v1.Object)log.Printf("%s pod updated to %s", oObj.GetName(), nObj.GetName())},DeleteFunc: func(obj interface{}) {mObj := obj.(v1.Object)log.Printf("pod deleted from store: %s", mObj.GetName())},})//启动informerpodInformer.Run(stopCh)
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,FullResyncPeriod: s.resyncCheckPeriod,RetryOnError: false,ShouldResync: s.processor.shouldResync,//注册回调函数HandleDeltas,后面从queue弹出数据的时候要用到Process: s.HandleDeltas,WatchErrorHandler: s.watchErrorHandler,}......s.controller.Run(stopCh)
}
代码位置:client-go/tools/cache/controller.go
func (c *controller) Run(stopCh <-chan struct{}) {defer utilruntime.HandleCrash()go func() {<-stopChc.config.Queue.Close()}()r := NewReflector(c.config.ListerWatcher,c.config.ObjectType,c.config.Queue,c.config.FullResyncPeriod,)// 省略代码......var wg wait.Group//启动reflectorwg.StartWithChannel(stopCh, r.Run)//启动processLoopwait.Until(c.processLoop, time.Second, stopCh)wg.Wait()
}
reflector开始list and watch,代码位置:client-go/tools/cache/reflector.go
func (r *Reflector) Run(stopCh <-chan struct{}) {klog.V(3).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)wait.BackoffUntil(func() {if err := r.ListAndWatch(stopCh); err != nil {r.watchErrorHandler(r, err)}}, r.backoffManager, true, stopCh)klog.V(3).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
}
switch event.Type {//watch到add事件case watch.Added:err := r.store.Add(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))}//watch到modified事件case watch.Modified:err := r.store.Update(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))}//watch到delete事件case watch.Deleted:// TODO: Will any consumers need access to the "last known// state", which is passed in event.Object? If so, may need// to change this.err := r.store.Delete(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))}case watch.Bookmark:// A `Bookmark` means watch has synced here, just update the resourceVersiondefault:utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))}
以update为例
func (f *DeltaFIFO) Update(obj interface{}) error {f.lock.Lock()defer f.lock.Unlock()f.populated = truereturn f.queueActionLocked(Updated, obj)
}
func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {id, err := f.KeyOf(obj)if err != nil {return KeyError{obj, err}}oldDeltas := f.items[id]newDeltas := append(oldDeltas, Delta{actionType, obj})newDeltas = dedupDeltas(newDeltas)if len(newDeltas) > 0 {if _, exists := f.items[id]; !exists {//将key放入到queuef.queue = append(f.queue, id)}//将newDeltas放入到items中f.items[id] = newDeltas//事件到达广播f.cond.Broadcast()} else {// This never happens, because dedupDeltas never returns an empty list// when given a non-empty list (as it is here).// If somehow it happens anyway, deal with it but complain.if oldDeltas == nil {klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; ignoring", id, oldDeltas, obj)return nil}klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; breaking invariant by storing empty Deltas", id, oldDeltas, obj)f.items[id] = newDeltasreturn fmt.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; broke DeltaFIFO invariant by storing empty Deltas", id, oldDeltas, obj)}return nil
}
DeltaFIFO的数据结构如下:
type DeltaFIFO struct {// lock/cond protects access to 'items' and 'queue'.lock sync.RWMutexcond sync.Cond// `items` maps a key to a Deltas.// Each such Deltas has at least one Delta.items map[string]Deltas// `queue` maintains FIFO order of keys for consumption in Pop().// There are no duplicates in `queue`.// A key is in `queue` if and only if it is in `items`.queue []string// populated is true if the first batch of items inserted by Replace() has been populated// or Delete/Add/Update/AddIfNotPresent was called first.populated bool// initialPopulationCount is the number of items inserted by the first call of Replace()initialPopulationCount int// keyFunc is used to make the key used for queued item// insertion and retrieval, and should be deterministic.keyFunc KeyFunc// knownObjects list keys that are "known" --- affecting Delete(),// Replace(), and Resync()knownObjects KeyListerGetter// Used to indicate a queue is closed so a control loop can exit when a queue is empty.// Currently, not used to gate any of CRUD operations.closed bool// emitDeltaTypeReplaced is whether to emit the Replaced or Sync// DeltaType when Replace() is called (to preserve backwards compat).emitDeltaTypeReplaced bool
}
到这里,已经将最新的数据推送到了DeltaFIFO的queue中,接下来看下怎么处理queue中的数据。
queue出队:
回到之前注册的processLoop
func (c *controller) processLoop() {for {//从queue弹出数据,交由process处理,也就是之前注册的handleDeltasobj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))if err != nil {if err == ErrFIFOClosed {return}if c.config.RetryOnError {// This is the safe way to re-enqueue.// 重新入队queuec.config.Queue.AddIfNotPresent(obj)}}}
}
func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {f.lock.Lock()defer f.lock.Unlock()for {for len(f.queue) == 0 {// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.// When Close() is called, the f.closed is set and the condition is broadcasted.// Which causes this loop to continue and return from the Pop().if f.closed {return nil, ErrFIFOClosed}//如果queue中没有数据,阻塞等待f.cond.Wait()}id := f.queue[0]f.queue = f.queue[1:]depth := len(f.queue)if f.initialPopulationCount > 0 {f.initialPopulationCount--}item, ok := f.items[id]if !ok {// This should never happenklog.Errorf("Inconceivable! %q was in f.queue but not f.items; ignoring.", id)continue}delete(f.items, id)// Only log traces if the queue depth is greater than 10 and it takes more than// 100 milliseconds to process one item from the queue.// Queue depth never goes high because processing an item is locking the queue,// and new items can't be added until processing finish.// https://github.com/kubernetes/kubernetes/issues/103789if depth > 10 {trace := utiltrace.New("DeltaFIFO Pop Process",utiltrace.Field{Key: "ID", Value: id},utiltrace.Field{Key: "Depth", Value: depth},utiltrace.Field{Key: "Reason", Value: "slow event handlers blocking the queue"})defer trace.LogIfLong(100 * time.Millisecond)}//处理数据,重点看下这个方法,进入HandleDeltaserr := process(item)if e, ok := err.(ErrRequeue); ok {f.addIfNotPresent(id, item)err = e.Err}// Don't need to copyDeltas here, because we're transferring// ownership to the caller.return item, err}
}
代码位置 client-go/tools/cache/shared_informer.go
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {s.blockDeltas.Lock()defer s.blockDeltas.Unlock()// from oldest to newestfor _, d := range obj.(Deltas) {switch d.Type {case Sync, Replaced, Added, Updated:s.cacheMutationDetector.AddObject(d.Object)//从本地缓存indexer中查询数据是否存在if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {//如果存在,则更新indexer中该数据if err := s.indexer.Update(d.Object); err != nil {return err}isSync := falseswitch {case d.Type == Sync:// Sync events are only propagated to listeners that requested resyncisSync = truecase d.Type == Replaced:if accessor, err := meta.Accessor(d.Object); err == nil {if oldAccessor, err := meta.Accessor(old); err == nil {// Replaced events that didn't change resourceVersion are treated as resync events// and only propagated to listeners that requested resyncisSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()}}}//分发监听者,通知监听updates.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)} else {//如果不存在,则在indexer中添加该数据if err := s.indexer.Add(d.Object); err != nil {return err}//分发监听者,通知监听adds.processor.distribute(addNotification{newObj: d.Object}, false)}case Deleted:if err := s.indexer.Delete(d.Object); err != nil {return err}//分发监听者,通知监听deletes.processor.distribute(deleteNotification{oldObj: d.Object}, false)}}return nil
}
func (p *sharedProcessor) distribute(obj interface{}, sync bool) {p.listenersLock.RLock()defer p.listenersLock.RUnlock()if sync {for _, listener := range p.syncingListeners {//往监听者加入数据listener.add(obj)}} else {for _, listener := range p.listeners {//往监听者加入数据listener.add(obj)}}
}
func (p *processorListener) add(notification interface{}) {p.addCh <- notification
}
数据分发到了监听者,那么监听者是什么时候注册的,又是怎么工作的呢?
联系到前面informer注册的eventHandler
podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: func(obj interface{}) {mObj := obj.(v1.Object)log.Printf("New pod added: %s", mObj.GetName())},UpdateFunc: func(oldObj, newObj interface{}) {oObj := oldObj.(v1.Object)nObj := newObj.(v1.Object)log.Printf("%s pod updated to %s", oObj.GetName(), nObj.GetName())},DeleteFunc: func(obj interface{}) {mObj := obj.(v1.Object)log.Printf("pod deleted from store: %s", mObj.GetName())},})
func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) {s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)
}
func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) {//省略代码//......//创建监听者listener := newProcessListener(handler, resyncPeriod, determineResyncPeriod(resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize)if !s.started {s.processor.addListener(listener)return}// in order to safely join, we have to// 1. stop sending add/update/delete notifications// 2. do a list against the store// 3. send synthetic "Add" events to the new handler// 4. unblocks.blockDeltas.Lock()defer s.blockDeltas.Unlock()//添加监听者s.processor.addListener(listener)for _, item := range s.indexer.List() {listener.add(addNotification{newObj: item})}
}
func (p *sharedProcessor) addListener(listener *processorListener) {p.listenersLock.Lock()defer p.listenersLock.Unlock()p.addListenerLocked(listener)if p.listenersStarted {//在不同的协程使监听者运行起来p.wg.Start(listener.run)p.wg.Start(listener.pop)}
}
func (p *sharedProcessor) addListenerLocked(listener *processorListener) {p.listeners = append(p.listeners, listener)p.syncingListeners = append(p.syncingListeners, listener)
}
func (p *processorListener) pop() {defer utilruntime.HandleCrash()defer close(p.nextCh) // Tell .run() to stopvar nextCh chan<- interface{}var notification interface{}for {select {case nextCh <- notification:// Notification dispatchedvar ok boolnotification, ok = p.pendingNotifications.ReadOne()if !ok { // Nothing to popnextCh = nil // Disable this select case}//联系前面distribute分发监听者的时候将notification发送到addChcase notificationToAdd, ok := <-p.addCh:if !ok {return}if notification == nil { // No notification to pop (and pendingNotifications is empty)// Optimize the case - skip adding to pendingNotificationsnotification = notificationToAddnextCh = p.nextCh} else { // There is already a notification waiting to be dispatchedp.pendingNotifications.WriteOne(notificationToAdd)}}}
}
func (p *processorListener) run() {// this call blocks until the channel is closed. When a panic happens during the notification// we will catch it, **the offending item will be skipped!**, and after a short delay (one second)// the next notification will be attempted. This is usually better than the alternative of never// delivering again.stopCh := make(chan struct{})wait.Until(func() {for next := range p.nextCh {//这里调用到用户定义的handler方法switch notification := next.(type) {case updateNotification:p.handler.OnUpdate(notification.oldObj, notification.newObj)case addNotification:p.handler.OnAdd(notification.newObj)case deleteNotification:p.handler.OnDelete(notification.oldObj)default:utilruntime.HandleError(fmt.Errorf("unrecognized notification: %T", next))}}// the only way to get here is if the p.nextCh is empty and closedclose(stopCh)}, 1*time.Second, stopCh)
}
最后看一下informer的详细全局设计
相关文章:

kubernetes-informer机制
一、概念 informer 是 client-go 中的核心工具包,在kubernetes中,各个组件通过HTTP协议跟 API Server 进行通信。如果各组件每次都直接和API Server 进行交互,会给API Server 和ETCD造成非常大的压力。在不依赖任何中间件的情况下࿰…...
LeetCode 2451. Odd String Difference【字符串,哈希表】简单
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...

切片工具tippecanoe的全网最详细的解释
1.下载和安装 tippecanoe工具是mapbox官方提供的一个服务端切片工具,因此它是运行在服务器上的,它比较友好的支持mac和linux机器。对于windows来讲,就比较麻烦了。 首先对于mac系统,你只需配置好自己的homebrew,保证homebrew能够正常下载东西。 然后只需要一个命令: …...

Linux系统初始化命令的备忘单,Linux运维工程师收藏!
在管理和维护Linux系统时,有一些常用的命令可以帮助您进行系统初始化和配置。这些命令涵盖了各种任务,包括系统设置、用户管理、软件安装和网络配置等。 本文将为您提供一个Linux系统初始化命令的备忘单,以便在需要时方便查阅和使用。 系统设…...

五月最近一次面试,被阿里P8测开虐惨了...
都说金三银四涨薪季,我是着急忙慌的准备简历——5年软件测试经验,可独立测试大型产品项目,熟悉项目测试流程...薪资要求?5年测试经验起码能要个20K吧 我加班肝了一页半简历,投出去一周,面试电话倒是不少&a…...

工业机器视觉缺陷检测工作小结
工业机器视觉检测工作小结 (因为网上没有很系统的讲义和文档,都是零零散散的,因此,我自己尝试着总结一下、仅供参考) 你想知道的大概率在这都可以找到、相机的了解镜头的了解光源的了解传统算法DL深度学习方法 &#…...
技术笔记:默默耕耘,赢得铁粉的秘密策略!
目录 第一步:真实实践,价值分享第二步:高质量文章的撰写第三步:积极互动,回复评论和留言第四步:定期更新和持续学习第五步:参与技术社区第六步:社区问答和问题解答总结 导语…...

回收站中怎么找回误删除的文件?这几种方法很实用
当我们在电脑上操作文件的时候,难免会有不小心删除文件的情况发生。这个时候,我们可以打开回收站来找回误删除的文件。但是,有时候我们也会误将回收站清空。那么,该怎样才能找回已经误删除的文件呢?在这里提供了回收站…...

Gateway网关参数进行验签POST 包含requestbody 请求体封装
Gateway网关自定义拦截器的不可重复读取数据 特别注意一点, 因为在网关层 拿出 request 流之后,必须重写getbody()方法把所有的参数放进去,否则后面转发的请求无法接收到任何数据, 坑,巨坑,因为版本问题网上很多都不能兼容, 我的springboot环境 依赖包 <parent><gr…...

【Netty】字节缓冲区 ByteBuf (六)(上)
文章目录 前言一、ByteBuf类二、ByteBuffer 实现原理2.1 ByteBuffer 写入模式2.2 ByteBuffer 读取模式2.3 ByteBuffer 写入模式切换为读取模式2.4 clear() 与 compact() 方法2.5 ByteBuffer 使用案例 总结 前言 回顾Netty系列文章: Netty 概述(一&…...

Python - 面向对象编程 - 实例方法、静态方法、类方法
实例方法 在类中定义的方法默认都是实例方法,前面几篇文章已经大量使用到实例方法 实例方法栗子 class PoloBlog:def __init__(self, name, age):print("自动调用构造方法")self.name nameself.age agedef test(self):print("一个实例方法&…...

性能测试——基本性能监控系统使用
这里写目录标题 一、基本性能监控系统组成二、环境搭建1、准备数据文件 type.db collectd.conf2、启动InfluxDB3、启动grafana4、启动collectd5、Grafana中配置数据源 一、基本性能监控系统组成 Collectd InfluxdDB Grafana Collectd 是一个守护(daemon)进程,用来…...
JavaCollection集合
5 Collection集合 5.1 Collection集合概述 是单列集合的顶层接口,它表示一组对象,这些对象也称Collection元素JDK不提供此接口的直接实现,它提供更具体的子接口(Set 和 List)实现package ceshi;import java.util.AbstractCollection; import java.util.ArrayList; import…...

C++中string的用法
博主简介:Hello大家好呀,我是陈童学,一个与你一样正在慢慢前行的人。 博主主页:陈童学哦 所属专栏:CSTL 前言:Hello各位小伙伴们好!欢迎来到本专栏CSTL的学习,本专栏旨在帮助大家了解…...
目标检测YOLO实战应用案例100讲-基于深度学习的交通场景多尺度目标检测算法研究与应用
目录 基于深度学习的交通目标检测算法研究 传统的目标检测算法 基于深度学习的目标检测算法 </...
面试:vue事件绑定修饰符
stop - 调用 event.stopPropagation()。 prevent - 调用 event.preventDefault()。 trim 自动过滤用户输入的首尾空格 number 将输出字符串转为Number类型 enter 回车键 capture - 添加事件侦听器时使用 capture 模式。 self - 只当事件是从侦听器绑定的元素本身触发时才触发…...
优思学院|从0到1,认识精益生产管理
精益生产是一种系统性的生产管理方法,旨在最大化价值,最小化浪费,以及提高产品质量和客户满意度。它源于丰田生产系统(TPS),是一种基于流程优化、以人为本的管理方法,强调优化生产流程、减少浪费…...
HashSet创建String类型的数据
package com.test.Test07;import java.util.HashSet;public class TestString {//这是一个main方法,是程序的入口public static void main(String[] args) {//创建一个HashSet集合HashSet<String> hs new HashSet<>();hs.add("hello");Syste…...

真会玩:莫言用ChatGPT为余华写了一篇获奖词
5月16日,《收获》杂志65周年庆典暨新书发布活动在上海舞蹈中心举行。 典礼现场,余华凭借《文城》获得收获文学榜2021年长篇小说榜榜首。 作为老友,莫言在颁奖时故意卖了个关子:“这次获奖的是一个了不起的人物,当然了&…...

10 工具Bootchart的使用(windows)
Bootchart的使用方法(windows) 下载bootchart.jar并拷贝到windows, 然后保证windows也安装了open jdk 1.8; 下载地址:https://download.csdn.net/download/Johnny2004/87807973 打开设备开机启动bootchart的开关: adb shell touch /data/boo…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...