在本系列的part 6中(如果需要,可以从头开始),我们得到了一个相当完整的sharedIndexInformer结构视图及其导致的所有概念。
现在是时候看看这一切的行为方面了。
在下图中,我使用了UML 2.0构造。 具体来说,填充箭头表示同步调用,实线上的空心箭头表示异步调用,虚线上的空心箭头表示返回值。 标记为alt的帧是遵循UML 2.0的条件分支点。 最后,我简化了一些刻板印象,我希望这些刻板印象是合情合理的。
此图以在sharedIndexInformer上调用Run函数的某人或某事开始。 Run函数创建一个新的DeltaFIFO,传递MetaNamespaceKeyFunc作为其KeyFunc,以及sharedIndexInformer的Indexer(它也是一个KeyLister和一个KeyGetter,但你不能只看代码)。
然后,Run函数创建一个新的Controller,我在part 2中详细介绍了它,并异步调用它的run函数。 sharedIndexInformer的Run函数现在阻塞,直到显式关闭。
Controller的run函数创建了一个Reflector,为了本系列的目的,您可以手动过滤:相信通过使用其嵌入式ListerWatcher,它可以准确地将Kubernetes资源放入其存储中,这恰好是之前创建的DeltaFIFO。
在这一点上,我们在高层次上拥有一台Rube Goldberg机器,可将Kubernetes资源复制到DeltaFIFO中。 例如,如果新的Pod出现在Kubernetes中,那么它会显示在DeltaFIFO中。
现在Controller的运行进入一个无限直到显式停止的循环,它每秒调用Controller的processLoop函数。
processLoop函数通过Reflector将单个线程中存储items的DeltaFIFO出队。 换句话说,DeltaFIFO是(如名称所示)缓冲队列,其中生产者通过Reflector实际上是Kubernetes本身,并且消费者(有效地)在构建时提供给Controller的任何功能。
那么在构建时,该特定Controller提供了哪些功能? sharedIndexInformer的handleDeltas函数。
因此,handleDeltas函数是一个队列排除器,并且出于许多行为分析的目的,我们可以方便地忽略之前的所有内容。 我们知道,当调用此函数时,它已从(有效)Kubernetes收到一组添加,更改,完全替换或删除。 这是它的样子:
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
s.blockDeltas.Lock()
defer s.blockDeltas.Unlock()
// from oldest to newest
for _, d := range obj.(Deltas) {
switch d.Type {
case Sync, Added, Updated:
isSync := d.Type == Sync
s.cacheMutationDetector.AddObject(d.Object)
if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
if err := s.indexer.Update(d.Object); err != nil {
return err
}
s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)
} else {
if err := s.indexer.Add(d.Object); err != nil {
return err
}
s.processor.distribute(addNotification{newObj: d.Object}, isSync)
}
case Deleted:
if err := s.indexer.Delete(d.Object); err != nil {
return err
}
s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
}
}
return nil
}
根据它正在处理的事情,它可以向另一个队列添加,更新或删除事件,这次是由驾驶整个列车的sharedIndexInformer创建的Indexer。 一旦Add,Update或Delete调用返回,它就会调用sharedIndexInformer的关联sharedProcessor上的distribute函数。
sharedProcessor的分发函数将通知转发给其相应的processorListeners,有效地将通知多路复用。 因此,如果给定的Delta对象是添加,则将调用processorListener的add函数。
processorListener add函数只是将传入通知放在名为addCh的同步队列(Go通道)上。 在这一点上,我们的通知之行暂时结束。
同时,回到sharedIndexInformer,回想一下它的Run方法仍在使用中。 在创建DeltaFIFO(现在正在入队和出队)和Controller(间接入队并耗尽它)之后,它执行的第三个有意义的事情是在单独的线程上调用其sharedProcessor上的run函数。
sharedProcessor运行函数产生另外两个线程,然后挂起,直到被告知关闭。 第一个线程调用sharedProcessor的每个processorListener上的run函数。 第二个线程在每个processorListener上调用pop方法。 我们先来看一下processListener的run函数。
processListener的run函数只是从其nextCh同步队列(Go通道)中拉出任何通知,并且根据它的类型,最终在用户提供的ResourceEventHandler上调用OnUpdate,OnAdd或OnDelete。 这很简单,我可以在这里重现它:
func (p *processorListener) run() {
defer utilruntime.HandleCrash()
for next := range p.nextCh {
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: %#v", next))
}
}
}
那么什么东西放在nextCh channel?pop函数。 此函数一直运行,直到被告知显式关闭,并将传入的通知从其addCh同步队列(Go通道)中拉出并将它们放在nextCh通道上。
那么什么把东西放在addCh channel呢? 查看几段并回想起这是我们Kubernetes事件之旅的逻辑结束:表示事件的通知由processorListener的add函数放置在addCh上,由sharedProcessor的distribute函数调用。
这似乎是结束这篇文章的时候了。 我建议您查看序列图的完整大小版本,并在重新阅读该系列时将其打印出来或保留在您旁边,此包中的许多结构将更有意义。