Kubernetes 事件队列

ESP32新手必看:Thonny+MicroPython固件刷写全流程(附闪灯代码) 本文为ESP32新手提供了使用Thonny IDE刷写MicroPython固件的完整流程指南。从开发环境搭建、固件版本选择,到详细的刷写步骤和常见问题解决方案,帮助开发者快速上手。文章还包含一个Web控制的智能LED实战项目,演示了如何将ESP32连接到网络并实现远程控制,是物联网原型开发的实用入门教程。 阅读详情

Kubernetes 事件队列

Queue 接口是在 Store 的基础上添加了 Pop() 方法。

FIFO 和 DeltaFIFO 类型(非接口)实现了 Queue 接口。

DeltaFIFO 是 Kubernetes 中非常重要的数据结构,用于保存对象的变化事件。

Queue 定义了队列接口

Queue 是在对象缓存的基础上,添加了 Pop() 方法,这样既能缓存对象、按照 Key 查找对象、也能按序(Add 的顺序)弹出对象:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
type Queue interface {
    // 对象缓存接口
    Store

    // 弹出队列中的一个对象,如果队列为空,则一直阻塞。返回处理后的对象,以及处理结果。
    Pop(PopProcessFunc) (interface{}, error)

    AddIfNotPresent(interface{}) error

    // 当队列中第一批对象都弹出后,返回 true。
    HasSynced() bool
    Close()
}

FIFO 是先入先出的队列

FIFO (struct 类型,非接口)实现了 Queue 接口,只缓存对象的一个最新值,例如,队列中对象 A 的值为 a1,在被弹出前,进行两次更新,值分别为 a2, a3,则只会弹出一次且值为 a3。

FIFO 适用的情况:

  1. 你希望最多一个 worker 处理某个对象(对象在队列中是唯一的);
  2. 当 worker 处理该对象时,对象值是最新的;
  3. 你不需要处理删除的对象(删除的对象不会被弹出);

FIFO 类型定义如下:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
type FIFO struct {
    lock sync.RWMutex
    cond sync.Cond

    // 对象缓存,用于快速查询。map key 为对象的 Key,该 Key 由 keyFunc 函数生成
    items map[string]interface{}

    // 对象弹出(Pop)顺序队列,队列中各对象 Key 是**唯一**的
    queue []string

    // 首先调用 Delete/Add/Update 或 Replace() 添加的第一批对象都 Pop 后为 true
    populated bool

    // Replace() 添加的第一批对象的数目
    initialPopulationCount int

    // 根据对象生成它的标识 Key 的函数
    keyFunc KeyFunc

    closed     bool
    closedLock sync.Mutex
}

函数 NewFIFO() 返回 FIFO 类型对象,传入的 KeyFunc (一般是 DeletionHandlingMetaNamespaceKeyFunc) 用于生成对象 Key:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func NewFIFO(keyFunc KeyFunc) *FIFO {
    f := &FIFO{
        // 初始化对象缓存
        items:   map[string]interface{}{},
        // 初始化对象 Key 队列
        queue:   []string{},
        keyFunc: keyFunc,
    }
    f.cond.L = &f.lock
    return f
}

Add() 方法

将对象更新到缓存(f.items),如果缓存中没有该对象,则将它加到弹出队列(f.queue),这样可以保证只会弹出对象一次,且弹出的是最新值。

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func (f *FIFO) Add(obj interface{}) error {
    id, err := f.keyFunc(obj)
    if err != nil {
        return KeyError{obj, err}
    }
    f.lock.Lock()
    defer f.lock.Unlock()
    f.populated = true

    // 缓存中没有该对象,则将它的 key 加到队列 f.queue 中
    if _, exists := f.items[id]; !exists {
        f.queue = append(f.queue, id)
    }
    // 更新对象缓存
    f.items[id] = obj
    f.cond.Broadcast()
    return nil
}

什么情况下缓存中没有该对象呢?

  1. 第一次向 FIFO Add/Update 该对象;
  2. 或者调用 FIFO 的 Delete 方法删除该对象;
  3. 或者,该对象被 Pop 处理了;
  4. 或者,调用 Replace 方法,用新的一组对象替换当前缓存 f.items 和队列 f.queue;

Update() 方法

通过 Add() 方法实现:

func (f *FIFO) Update(obj interface{}) error {
    return f.Add(obj)
}

Delete() 方法

从缓存中删除对象:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func (f *FIFO) Delete(obj interface{}) error {
    id, err := f.keyFunc(obj)
    if err != nil {
        return KeyError{obj, err}
    }
    f.lock.Lock()
    defer f.lock.Unlock()
    f.populated = true
    // 从缓存中删除对象,注意 f.queue 中还可能有对象的 Key
    delete(f.items, id)
    return err
}

注意:没有从弹出队列(f.queue) 中删除该对象 Key,后续弹出过程中会忽略这种已删除对象的 Key,继续弹出下一个对象。

Pop() 方法

从弹出队列(f.queue)弹出一个对象,并调用用户注册的回调函数进行处理,返回处理后的对象和出错信息。

如果队列为空,则一直阻塞。

处理函数执行失败时应该返回 ErrRequeue 类型的错误,这时该对象会被重新加回 FIFO,后续可以再次被弹出处理。

Pop() 是在对 FIFO 加锁的情况下调用处理函数的,所以可以在多个 goroutine 中并发调用 该方法。

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) {
    f.lock.Lock()
    defer f.lock.Unlock()
    for {
        for len(f.queue) == 0 {
            if f.IsClosed() {
                return nil, FIFOClosedError
            }
            // 如果队列未关闭,但为空,则阻塞等待
            f.cond.Wait()
        }
        // 先从 queue 弹出对象 id
        id := f.queue[0]
        f.queue = f.queue[1:]
        if f.initialPopulationCount > 0 {
            f.initialPopulationCount--
        }
        // 从缓存中获取对象
        item, ok := f.items[id]
        if !ok {
            // 前面提到,当 Add/Update 对象,在 Pop 前又 Delete 了该对象,就会出现 queue 中有 id,而 items 中无对象的情况
            // 由于对象已经被删除,所以跳过,Pop 下一个对象
            continue
        }
        // 从缓存中删除对象
        delete(f.items, id)
        // 调用处理函数,该函数处于 f.lock 锁保护中,可以并发执行
        err := process(item)
        // 如果处理 item 失败,应该返回 ErrRequeue 类型错误,再将对象加回队列
        if e, ok := err.(ErrRequeue); ok {
            f.addIfNotPresent(id, item)
            err = e.Err
        }
        return item, err
    }
}

Replace() 方法

用传入的一组对象替换对象缓存 f.items 和弹出队列 f.queue:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func (f *FIFO) Replace(list []interface{}, resourceVersion string) error {
    items := make(map[string]interface{}, len(list))
    for _, item := range list {
        key, err := f.keyFunc(item)
        if err != nil {
            return KeyError{item, err}
        }
        items[key] = item
    }

    f.lock.Lock()
    defer f.lock.Unlock()

    if !f.populated {
        f.populated = true
        f.initialPopulationCount = len(items)
    }

    f.items = items
    f.queue = f.queue[:0]
    for id := range items {
        f.queue = append(f.queue, id)
    }
    if len(f.queue) > 0 {
        f.cond.Broadcast()
    }
    return nil
}

HasSyncd() 方法

参考后文对 DeltaFIFO 的 HasSyncd() 方法分析。

Resync() 方法

将对象缓存 f.items 中的对象都更新到弹出队列 f.queue 中:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func (f *FIFO) Resync() error {
    f.lock.Lock()
    defer f.lock.Unlock()

    inQueue := sets.NewString()
    for _, id := range f.queue {
        inQueue.Insert(id)
    }
    for id := range f.items {
        if !inQueue.Has(id) {
            f.queue = append(f.queue, id)
        }
    }
    if len(f.queue) > 0 {
        f.cond.Broadcast()
    }
    return nil
}

FIXME!!!:对象加入和弹出时都会同时更新 f.items 和 f.queue,按说是完全一致的,所以 Resync() 方法是多余的?

DeltaFIFO 是记录对象历史事件的队列

DeltaFIFO 与 FIFO 类型的区别:

  1. DeltaFIFO 缓存对象的事件列表,而 FIFO 缓存对象的最新值;
  2. FIFO 内部维护了一个对象缓存(f.items),而 DeltaFIFO 需要和一个外部维护的、包含所有对象的缓存(knownObjects) 结合使用:
    • Delete():用 knownObjects 检查待删除的对象是否存在,如果不存在则直接返回,否则生成 Deleted 事件;
    • Replace():用传入的队列列表更新 DeltaFIFO,检查 knownObjects,为不在传入的对象列表中的对象生成 Deleted 事件;
    • Resync():将 knownObjects 中的对象同步到 DeltaFIFO 中,并生成 Sync 事件;
  3. Delete/Replace/Resync() 方法不会从 DeltaFIFO 删除/替换对象,而是生成对应的事件。DeltaFIFO 的消费者需要将他们从 knownObjects 删除(见后文);
  4. DeltaFIFO 的 Pop/Get() 方法,返回的不是对象最新值,而是对象事件列表。

DeltaFIFO 适用的情况:

  1. 你希望最多一个 worker 处理某个对象的事件(对象在队列中是唯一的,);
  2. 当 worker 处理该对象时,可以获得自上次弹出该对象以来的所有事件,如 Add/Updat/Delete(FIFO 只缓存和弹出对象的最新值);
  3. 你可以处理删除对象的事件(FIFO 不弹出被删除的对象);
  4. 你想周期处理所有的对象(Reflector 周期调用 Resync() 方法,将 knownObjects 中对象同步到 DeltaFIFO);

DeltaFIFO 是一个生产者-消费者队列,生产者是 Reflector,消费者是 controller/sharedInformer/sharedIndexInformer。

函数 NewDeltaFIFO() 返回一个 DeltaFIFO 类型对象:

// 来源于 k8s.io/client-go/tools/cache/delta_fifo.go
func NewDeltaFIFO(keyFunc KeyFunc, knownObjects KeyListerGetter) *DeltaFIFO {
    f := &DeltaFIFO{
        // 对象事件缓存,Key 为对象 Key,Value 为该对象的事件列表类型 Deltas;
        items:        map[string]Deltas{},
        // 对象弹出队列,缓存的是对象 Key,后续 Pop 方法按序弹出;
        queue:        []string{},
        // 生成对象标识 Key 的函数,一般是预定义的 MetaNamespaceKeyFunc 函数;
        keyFunc:      keyFunc,
        // 关联的外部对象缓存
        knownObjects: knownObjects,
    }
    f.cond.L = &f.lock
    return f
}
  • knownObjects 是外部的对象缓存,DelaFIFO 不对它进行更新,只用它来查找对象。
  • keyFunc 一般是预定义的 MetaNamespaceKeyFunc 函数,即提取对象的 Namespace/Name 作为标识 Key。

例如 NewIndexerInformer() 创建 knownObjects 和 DeltaFIFO 的过程如下:

// 来源于:k8s.io/client-go/tools/cache/controller.go
...
clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
...
fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, clientState)

DeltaFIFO 的消费者根据从 DeltaFIFO 弹出的 Delta 对象对 knownObjects 缓存(上面的 clientState )进行更新,从而保证 FIFO 缓存和该缓存的一致性。

DeltaFIFO 的生产者和消费者

后续文章会介绍,各种 Informer(如 Informer、IndexInformer、SharedInformer、SharedIndexInformer)的初始化函数依次创建
knownObjects 缓存、DeltaFIO 和 controller。controller 再将 DeltaFIFO 传给 Reflector,

Reflector 的 ListAndWatch() 方法是 DeltaFIFO 的生产者:

  1. List etcd 中(通过 kube-apiserver,下同)特定类型的所有对象,然后调用 DeltaFIFO 的 Replace() 方法,将它们完整同步到 DeltaFIFO;
  2. 根据配置的 Resync 时间,周期调用 DeltaFIFO 的 Resync() 方法(见后文),将 knownObjects 中的对象更新到 DeltaFIFO 中;
  3. 阻塞式 Watch etcd 中特对类型的对象变化,根据事件的类型分别调用 DeltaFIFO 的 Add/Update/Delete()方法,将对象更新到 DeltaFIFO;

Watch etcd 会周期性的超时(5min ~ 10min),这时 ListAndWatch() 出错返回,Reflector 会等待一段时间再执行它,从而实现周期的将 etcd 中特定类型的全部对象同步到 DeltaFIFO。

controller 是 DeltaFIFO 的消费者,它用 DeltaFIFO 弹出的对象更新 knownObjects 缓存,然后调用注册的 OnUpdate/OnAdd/OnDelete 回调函数。
详情参考 Reflector 和 controller 和 Informer 文档。

记录对象事件的 Delta、Deltas 和 DeletedFinalStateUnknown 类型

DeltaFIFO 使用 Delta 类型记录对象的事件类型和发生事件后的对象值:

type Delta struct {
    // DeltaType 可能是:Added、Deleted、Updated、Sync
	Type   DeltaType
	Object interface{}
}

DeltaFIFO Watch apiserver 过程中可能因网络等问题出现丢事件的情况,如果丢失了 Delete 事件,则后续 Reflector 重复执行 ListAndWatch() 方法从 apiserver 获取的对象集合 set1 会出现与 knownObjects 对象集合 set2 不一致的情况。
为了保证两者一致,DeltaFIFO 的 Replace() 方法将位于 set1 但不在 set2 中的对象用 DeletedFinalStateUnknown 类型对象封装,再保存到 Delta Object 中。

type DeletedFinalStateUnknown struct {
    // 对象的 Key
    Key string
    // knownObjects 缓存中的对象值
    Obj interface{}
}

Replace() 方法是唯一产生 DeletedFinalStateUnknown 类型对象的方法。

Add() 和 Update() 方法

// 来源于 k8s.io/client-go/tools/cache/delta_fifo.go
func (f *DeltaFIFO) Add(obj interface{}) error {
    f.lock.Lock()
    defer f.lock.Unlock()
    f.populated = true
    // Added 类型事件;
    return f.queueActionLocked(Added, obj)
}

Update() 方法和 Add() 方法类似,差别在于产生的是 Updated 类型 Delta 事件;

queueActionLocked() 方法

将对象的事件存入事件队列 f.items,如果事件队列中没有该对象则还将对象(Key)加入弹出队列(f.queue),另外它还做如下操作:

  1. 如果事件类型为 Sync,且对象的事件列表中最后一个事件类型为 Deleted,则直接返回(没有必要再同步一个已删除的对象,一般是 Reflector 周期调用 Rsync() 方法产生的 Sync 事件);
  2. 合并连续重复的 Deleted 事件为一个;
func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {
    id, err := f.KeyOf(obj)
    if err != nil {
        return KeyError{obj, err}
    }

    // FIXME!!! 感觉这个逻辑不太对。产生 Sync 事件有两种情形:
    // 1. 周期的 Rsync(), 这时处理逻辑 OK;
    // 2. Reflector 执行 ListAndWatch() LIST etcd 获取特定类型的全部对象;
    if actionType == Sync && f.willObjectBeDeletedLocked(id) {
        return nil
    }
    // 将对象保存到 Delta 中
    newDeltas := append(f.items[id], Delta{actionType, obj})
    newDeltas = dedupDeltas(newDeltas)

    if len(newDeltas) > 0 {
        if _, exists := f.items[id]; !exists {
            f.queue = append(f.queue, id)
        }
        f.items[id] = newDeltas
        f.cond.Broadcast()
    } else {
        delete(f.items, id)
    }
}

Delete() 方法

如果 f.knownObjects 对象缓存和事件队列 f.items 中均没有待删除的对象,则直接返回,否则为对象生成 Deleted 事件(非DeletedFinalStateUnknown 类型)。

FIFO 的 Delete() 方法将对象从缓存中删除,而 DeltaFIFO 的 Delete() 方法不将对象从事件缓存 f.items 和弹出队列 f.queue 删除,而是后续弹出时,将它从 f.items、f.queue 和 f.knownObjects 中删除。

Get/GetByKey/List/ListKeys() 方法

查询事件缓存 f.items,返回对象的事件列表 Deltas 或 Key 列表;

Replace() 方法

Replace(list []interface{}, resourceVersion string)

  1. 为 list 中的每个对象生成 Sync 事件;
  2. 遍历 f.knownObjects 中对象,对不在传入的 list 中的对象,用 DeletedFinalStateUnknown 类型对象封装,再保存到 Deleted 类型的 Delta.Object 中;

Reflector 的 ListAndWatch() 方法因 Watch 超时而周期调用 Replace() 方法,从而周期地将 etcd 中特定类型的所有对象同步到 DeltaFIFO 中。
controller 用 DeltaFIFO 弹出的对象更新 knownObjects 缓存,详情参考 Reflector 和 controller 和 Informer 文档。

Replace() 方法是唯一产生 DeletedFinalStateUnknown 类型对象的方法。

Resync() 方法

遍历 knownObjects 中的对象:

  1. 如果该对象位于事件缓存 f.items 中,则跳过;
  2. 否则,生成 Sync 事件;

前文我们提到 DeltaFIFO 的使用场景之一是:“你想周期处理所有的对象”,而这是通过周期将 knownObjects 中的对象同步到 DeltaFIFO 来实现的。

Reflector 的 ListAndWatch() 方法周期执行 DeltaFIFO 的 Resync() 方法,将 knownObjects 中的对象同步到 DeltaFIFO(产生 Sync 事件),从而有机会再次调用注册的 OnUpdate() 处理函数。

只有 Replace() 和 Rsync() 方法才产生 Sync 事件。

Pop() 方法

Pop(process PopProcessFunc)

  1. 如果弹出队列 f.queue 为空,则阻塞等待;
  2. 每次弹出队列头部对象的事件列表(Deltas 类型),然后将该对象的事件列表从缓存(f.items)中删除;
  3. 调用配置的回调函数 PopProcessFunc(传入事件列表 Deltas);

如果函数 PopProcessFunc() 执行失败,应该调用 AddIfNotPresent() 方法将 Deltas 重新加回 DeltaFIFO,这样后续可以再次被弹出处理,防止丢事件。(controler 已实现该逻辑)

注意,Pop() 方法是在加锁的情况下调用 PopProcessFunc 的,所以在多个 goroutine 并发调用 Pop() 的情况下,它们实际是串行执行的。

HasSyncd() 方法

创建 DealtaFIFO 后,如果首先调用的是 Replace() 方法,则 f.populated 被设置为 true,f.initialPopulationCount 被设置为传入的对象数量。当这第一批对象都被弹出完毕时(包含弹出前被删除的对象),HasSynced() 方法返回 true:

// 来源于 k8s.io/client-go/tools/cache/fifo.go
func (f *DeltaFIFO) HasSynced() bool {
    f.lock.Lock()
    defer f.lock.Unlock()
    return f.populated && f.initialPopulationCount == 0
}

另外,如果在调用 Replace() 方法前,首先调用了 Add/Update/Delete/AddIfNotPresent() 方法,则 HasSynced() 方法也会返回 true。

第一批对象弹出完毕后,后续不管是否再次调用 Replace()或其它方法,HasSynced() 方法总是返回 true

DeltaFIFO 和 knownObjects 对象缓存的同步

  1. Reflector 从 etcd List 出特定类型的所有对象,调用 DeltaFIFO 的 Replace() 方法为各对象生成 Sync 事件,此时 knownObjects 对象缓存为空;

  2. controller 从 DeltaFIFO 弹出对象事件列表 Deltas,遍历 Deltas,根据 Delta 的事件类型更新 knownObjects,从而实现 DeltaFIFO 和 knownObjects 缓存中的对象一致:

    controller 每次启动时,因 knownObjects 为空且事件类型为 Sync,所以会为同步来的所有对象:

    1. 调用 knownObjects 的 Add() 方法,将它们加入到对象缓存;
    2. 调用注册的 OnAdd() 回调函数。所以第一次对象同步时, controller 也会调用用户注册的 OnAdd() 回调函数。
    // 来源于:k8s.io/client-go/tools/cache/controller.go
    for _, d := range obj.(Deltas) {
        switch d.Type {
        // Replace() 方法生成的 Sync 事件涉及到的对象,
        case Sync, Added, Updated:
            // clientState 即为 knownObjects 对象缓存
            if old, exists, err := clientState.Get(d.Object); err == nil && exists {
                if err := clientState.Update(d.Object); err != nil {
                    return err
                }
                h.OnUpdate(old, d.Object)
            } else {
                if err := clientState.Add(d.Object); err != nil {
                    return err
                }
                h.OnAdd(d.Object)
            }
        case Deleted:
            // 先从缓存中删除,再调用处理函数
            if err := clientState.Delete(d.Object); err != nil {
                return err
            }
            h.OnDelete(d.Object)
        }
    }
    
  3. 但是,Reflector 的 Watch 可能会出现丢失事件的情况(如 ListAndWatch 出错返回后,Reflector 会 Sleep 一段时间再执行它,期间 etcd 的对象变化事件丢失),这样再次 List 到的对象集合 set1 与 knownObjects 缓存中的对象集合 set2 不一致。如何解决这个问题呢?

    答案在于:List 到对象集合后,DeltaFIFO 调用的 Replace() 方法将位于 set1 但不在 set2 中的对象用 DeletedFinalStateUnknown 类型对象封装,再保存到 Delta.Object 中。而上面 handlerObject() 的参数即为 Delta.Object。

  4. ListAndWatch() 方法起一个 goroutine,周期调用 Resync() 方法,将 knownObjects 中的对象更新到 DeltaFIFO。为何要这么做呢?

    前文我们提到 DeltaFIFO 的使用场景之一是:“你想周期处理所有的对象”,但对象一旦从 DeltaFIFO 中弹出,如果没有产生新的 Watch 事件,就不会对它再调用注册的回调函数。Reflector 的 ListAndWatch() 方法会周期执行 DeltaFIFO 的 Resync() 方法,目的就是为对象产生新的 Sync 事件,从而有机会再次调用注册的 OnUpdate() 处理函数。因此 Resync 时,如果对象已经在 f.items,则后续由机会被弹出,所以不需要为它生成 Sync 事件。

后续文章会介绍,controller 一般是在 Informer 中使用的,controller 调用的 OnUpdate() 函数会调用用户创建 Informer 时传入的 ResourceEventHandler 中的 OnUpdate() 函数。所以用户的 OnUpdate() 函数可能会因 DeltaFIFO 的周期 Resync() 而被调用,它应该检查传入的 old、new 对象是否发生变化,未变化时直接返回:

// 来源于 https://github.com/kubernetes/sample-controller/blob/master/controller.go#L131
deployInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    // 第一次从 etcd 同步的对象会生成 Added 事件,调用该函数
    // 后续 Added 事件就代表的确有**新创建**的资源对象;
    AddFunc: controller.handleDeploy,
    UpdateFunc: func(old, new interface{}) {
        // 由于 Reflector 周期调用 DeltaFIFO 的 Rsync() 方法,`controller` 会调用注册的 OnUpdate 回调函数,所以需要判断新旧对象是否相同,如果相同则不需处理
        // 也可以用
        newDepl := new.(*extensionsv1beta1.Deployment)
        oldDepl := old.(*extensionsv1beta1.Deployment)
        if newDepl.ResourceVersion == oldDepl.ResourceVersion {
            // Periodic resync will send update events for all known Deployments.
            // Two different versions of the same Deployment will always have different RVs.
            return
        }
        controller.handleDeploy(new)
    },
    DeleteFunc: controller.handleDeploy,
})

另外,前面的第 2 条提到过,controller 刚启动时,knownObjects 为空,会为从 etcd 同步来的特定类型的所有对象生成 Added 事件,进而调用上面注册的 AddFunc。

别再死记硬背了!用Go写一个Operator,带你亲手拆解Informer的缓存与事件机制 本文通过Go语言实战演示如何构建K8s Operator,深入解析Informer的缓存与事件驱动机制。从List/Watch原理到DeltaFIFO队列实现,再到本地缓存Indexer优化,手把手教你掌握Operator开发核心技巧,提升K8s扩展开发能力。 阅读详情

相关推荐

【AI云原生】7、Client-go Informer机制解析:从基础到高级,构建高性能K8s控制器

本文介绍了Kubernetes客户端开发中的核心组件Informer,它通过List/Watch机制和本地缓存实现了高效资源监控。文章从轮询与事件驱动的对比切入,详细解析了Informer的架构设计(包含Reflector、Delta FIFO、Indexer等组件)和工作流程,并提供了基础Informer的代码实现示例。Informer能显著降低API Server压力,实现秒级事件响应,是构建K8s控制器和监控系统的关键技术。

2025博客之星Top81。专注AI工程化与架构实战。从分布式思维到模型部署,用工程化视角为你厘清AI落地的真实路径。 1818

API-Server的监听器Controller的List分页失效

最近做项目,还是K8S的插件监听器(理论上插件都是通过API-server通信),官方的不同写法居然都能出现争议,争议点就是对API-Server的请求的耗时,说是会影响API-Server。实际上通过源码分析两着有差别,但是差别不大,对API-Server的影响几乎一样。知其然知其所以然,要想知道为什么分页不生效,需要自定义API-Server debug才行,看代码很难看出原因,因为K8S实际上估计设计的时候也考虑过这个。

fenglllle的博客 816

Kubernetes控制器的通用工作模式(Reconcile Loop)【20260530】002篇

我再按你代码把:哪些地方会穿透 API Server、queue 为什么会堆、以及该加 selector / predicate / 并行度 / status 写入策略 给你点名到行号级别。后面说的“Controller 进程”= 这个 Pod/容器。你的 Controller/Operator 通常跑成。

老韩的Linux云计算架构师进阶之路 100

如何用 client-go 拓展 Kubernetes 的 API 【转载】

本文是转载信息如下,如有侵权,立删,原作者信息 章骏| 才云科技云开源软件工程师毕业于武汉大学软件工程专业,之前就职于百度,担任算法策略研发工程师,主要负责搜索相关性的指标。加入才云科技后,现主要负责负载均衡和灰度发布的工作。 今天给大家介绍一下如何使用 client-go 来拓展 Kubernetes API,写一个 Kubernetes 的控制器。 client-go 是 Kuber...

x_i_y_u_e的专栏 621

告别混乱!AutoSar BswM模式管理实战:手把手教你配置Rule与ActionList(附避坑指南)

本文详细解析了AutoSar BswM模式管理的实战配置,重点介绍了Rule与ActionList的高效组合方法,帮助开发者避免常见陷阱。通过具体案例演示了从基础到复杂的配置步骤,包括模式仲裁、逻辑表达式构建及ActionList优化策略,提升汽车电子系统的协调性与可靠性。

weixin_27066963的博客 258

高速SerDes均衡技术解析

均衡技术是高速SerDes链路中补偿信道损耗和失真的核心技术,通过FFE、CTLE和DFE三种主要方法对抗码间干扰(ISI)。FFE在发射端预补偿前光标干扰,CTLE通过提升高频增益补偿信道损耗,DFE则精准消除后光标干扰。三种技术各具优势:FFE实现简单且不放大噪声,CTLE功耗低易于集成,DFE能自适应调整且保持信噪比。这些技术协同工作可有效提升系统传输速率和可靠性。

一只豌豆象的博客 1945

informer机制详解

x

zxyuliwuzhognzx11的博客 877

图自编码器(GAE)与变分图自编码器(VGAE)

在学习图自编码器之前先了解一下自编码器与变分自编码器。

小鱼的小窝 1万+

WIP: DeltaFIFO

Queue 接口抽象出一个生产者消费者队列,用来解耦对 kubernetes 资源的获取和处理: Reflector 作为生产者,将从 apiserver 中获取到的资源对象存放到 Queue 中 Controller 作为消费者,调用 Pop 方法进行消费。 Queue 在 Store 接口的基础上又提供了四个方法用于队列的消费 type Store interface { Add(obj interface{}) error Update(obj interface{}) error Del

IceberGu的博客 566

CentOS 9 Stream 上安装 MySQL 8.0+

根据提示进行设置,包括设置新的 root 密码、禁用远程 root 登录等。MySQL 在首次安装时会生成一个临时密码。这样就完成了 MySQL 8.0.37 的安装。这将安装最新的 MySQL 8.0 版本。

小强不是坑 1677

Java后端中的链路追踪:使用OpenTelemetry实现全链路监控

在现代的微服务架构中,链路追踪是监控和优化系统性能的关键技术。通过链路追踪,我们可以跟踪请求在系统中的流转,识别性能瓶颈,定位问题根源。本文将介绍如何在Java后端应用中使用OpenTelemetry实现全链路监控,包括具体的实现步骤和代码示例。通过集成OpenTelemetry,您可以实现全面的链路追踪,帮助您识别瓶颈和潜在问题,从而提升系统的可靠性和用户体验。OpenTelemetry是一个开源项目,旨在提供统一的工具集来收集、处理和导出应用程序的遥测数据,包括跟踪、度量和日志。

聚娃科技开发者博客 1726

深入浅出kubernetes之client-go的DeltaFIFO

记得大学刚毕业那年看了侯俊杰的《深入浅出MFC》,就对深入浅出这四个字特别偏好,并且成为了自己对技术的要求标准——对于技术的理解要足够的深刻以至于可以用很浅显的道理给别人讲明白。以下内容为个人见解,如有雷同,纯属巧合,如有错误,烦请指正。 本文基于kubernetes1.11版本,后续会根据kubernetes版本更新及时更新文档,所有代码引用为了简洁都去掉了日志打印相关的代码,尽量只保留有价值...

weixin_42663840的博客 2万+

VS2103+Intel2013+MSMPI运行Fortran并行程序

VS2103+Intel2013+MSMPI运行Fortran并行程序MSMPI安装新建Fortran90项目编写Fortran并行程序在cmd运行编译完成的执行文件 MSMPI安装 我这里采用的是Microsoft MPI v9.0.1,可以点此下载。下载完成后同时安装msmpisdk.msi、msmpisetup.exe两个文件,安装位置默认即可。 其中msmpisdk.msi的默认安装路径在C:\Program Files (x86)\Microsoft SDKs\MPI,该文件包含了mpi环境所需

weixin_43176703的博客 2936

Kubewatch常见问题排查:从配置错误到性能优化的全面指南

Kubewatch是一款强大的Kubernetes事件监控工具,能够实时追踪K8s集群事件并通过多种通知渠道发送告警。本文将为您提供一份全面的问题排查指南,帮助您快速定位和解决Kubewatch在使用过程中可能遇到的各类问题,从基础的配置错误到高级的性能优化,让您的Kubernetes事件监控系统始终保持高效稳定运行。 ## Kubewatch工作原理简介 在开始排查问题之前,让我们先了解一下

gitblog_00533的博客 358

LTE网络下的SMS收发(SMS Over SGs方案的详细流程)

关键词: IMS,SMS, LTE, MSC, MME, EPS,eNodeB, SMS Over SGs, SMS Over IMS 2G时代由于采用了比较复杂的CS/PS域架构,所以SMS(短信)作为上个世纪的产物,一直寄居在2G的CS域架构下。看看核心网的配置,其实SMS的收发还是比较复杂的。 介绍下面的信息之前你需要去百度一下这些关键词,这里我只说明比较重要的三个单元 MS 移动终端...

kevin的博客 2万+

MIKE水动力笔记8_冷启动与热启动

在【MIKE水动力笔记5_建立水动力模型】中,讲到【Step 2 设置模型参数】中的【Initial Conditions】时,提到了冷启动与热启动。本博文就详细讲解何为冷启动、热启动,并通过一个案例具体分析两者不同。...

晏长街的博客 6567

Microsoft KEDA为Kubernetes带来了事件驱动的扩展

微软已经发布了 KEDA 1.0,这是KEDA的生产就绪版本,它是Kubernetes的开源组件,可为容器提供事件驱动的自动缩放。 伸缩基于事件或消息队列的大小,例如Apache Kafka或RabbitMQ。 Kubernetes已经具有根据需求扩展容器的能力。 但是,开箱即用的Kubernetes仅支持基于CPU和内存需求的自动缩放。 缩放其他触发器(例如,事件队列中等待消息的数量)需...

cxu0262的博客 284

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

ontroller-runtime:从 informer 到调谐的性能之道

比如,重联期间发生了删除了资源,该资源的删除事件并不会被 client-go 接收,后续也不会补偿,这个事件丢了。但是在重联后,全量 list 资源,发现缓存中的资源在 list 列表已不存在,client-go 会自己发一个 Delete 事件至 FIFO 用于删除缓存中的资源。B 在 t3 时刻更新,但是在 queue 队列中 B 只能位于 A 之前或之后,消费者在处理时并不会先处理 A 的 Add 和 Update 事件在去处理 B 的 Update 事件,即便这是真实的事件顺序。

iDm27zWrZ的博客 235

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

k8s garbage collector源码分析(1)-启动分析

k8s gc分析。Kubernetes garbage collector即垃圾收集器,存在于kube-controller-manger中,它负责回收kubernetes中的资源对象,监听资源对象事件,更新对象之间的依赖关系,并根据对象的删除策略来决定是否删除其关联对象。

良凯尔的博客 536

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

k8s源码分析之垃圾回收控制器

一般来说,垃圾回收(GC)就是从系统中删除不再使用的对象,并释放分配给它们的计算资源。GC 存在于所有的高级编程语言中,较低级的编程语言通过系统库实现 GC。终止的 Pod已完成的 Job附属的主对象已经不存在的对象未使用的容器和容器镜像StorageClass 回收策略为 Delete 的 PV 卷过期的证书签名。

chaoyueshijian的博客 1391

k8s garbage collector源码分析(2)-处理逻辑分析

k8s gc分析。Kubernetes garbage collector即垃圾收集器,存在于kube-controller-manger中,它负责回收kubernetes中的资源对象,监听资源对象事件,更新对象之间的依赖关系,并根据对象的删除策略来决定是否删除其关联对象。

良凯尔的博客 551

从单臂到三层:VLAN间通信的演进与VLAN聚合实战解析

本文深入解析了企业网络从单臂路由升级到三层交换的必要性,重点阐述了VLANIF虚接口作为三层交换核心的工作原理与配置实战。针对传统VLAN划分导致的IP地址浪费问题,文章详细介绍了VLAN聚合(Super-VLAN)技术,通过结合代理ARP实现多个子VLAN共享同一IP网段,并探讨了其与端口隔离技术的组合应用,以构建高效、安全且节省地址空间的现代企业网络。

java5的博客 418

client-go 与 controller-runtime:从 informer 到调谐的性能之道

比如,重联期间发生了删除了资源,该资源的删除事件并不会被 client-go 接收,后续也不会补偿,这个事件丢了。B 在 t3 时刻更新,但是在 queue 队列中 B 只能位于 A 之前或之后,消费者在处理时并不会先处理 A 的 Add 和 Update 事件在去处理 B 的 Update 事件,即便这是真实的事件顺序。笔者当时不知道,直到深挖了源码才知道,这里的注册只是告诉 client-go 资源的 gvk 和对象的转换关系等,解决的是资源的转换/序列化/反序列化问题。这种方式可以工作,但是有问题。

cm04Z9c91的博客 228

Go-Client源码 Informer机制

Go-Client源码 Informer机制

weixin_42279748的博客 284

K8S系列学习之开篇漫谈

分享目的 如上一贴所讲,工作之余学习K8S已有月余,确实略懂皮毛,之所以想到分享,一是本着开源社区的精神,二是权当在线留作记录,保存一些个人的学习所得,兴许理解粗浅,兴许神来之笔,或许将来有一天,工作中有用得上的时候,翻出来再学习学习,也是一种收获。如果还能够帮到别人,也是一种造化。 K8S架构 K8S全称是Kubernetes,其中K和S之间正好8个字母,所以在华语圈内简称K8S。至于它的前世今生,官方社区和网上有很多介绍的,此处就不再赘述。 说到架构,先从容易理解的“物理”层面介绍,因为K8S生

tianyangqi的专栏 518

open-falcon实现对k8s集群的监控

1. 前言当我们的k8s要面临落地时,监控和日志肯定时不可缺少的。它主要为了帮助系统运维人员事前及时预警发现故障,事后通过翔实的数据追查定位问题。2.可选方案:2.1 Heapster(数据采集自cAdvisor)+Influxdb(存储)+Grafana(展示) 这套方案缺点是没有报警功能2.2 Prometheus+Grafana 参考:http://blog.youkuaiyun.com/zqg525

wjs7740的专栏 3046
上一篇: Kubernetes 对象缓存和索引
下一篇: kubernetes 事件反射器
KingDragon_Baron
博客等级 码龄12年 55粉丝 73原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值