tools/cache 深度分析(中篇)--- 辅助组件逐行解析
基于
client-go v0.36.1tools/cache/源码
四、辅助组件深度解析
4.1 ExpirationCache --- TTL 自动过期缓存 (expiration_cache.go, 224行)
4.1.1 结构体
go
type ExpirationCache struct {
cacheStorage ThreadSafeStore // 底层存储(threadSafeMap)
keyFunc KeyFunc // key 提取函数
clock clock.Clock // 可注入时钟
expirationPolicy ExpirationPolicy // 过期判定策略
// expirationLock: 写锁,保证过期检查和删除的原子性
// 防止:检查过期→删除之间有新 item 插入同一 key
expirationLock sync.Mutex
}
4.1.2 核心机制:惰性过期
ExpirationCache 不在后台定期清理 ,而是在每次读取时检查是否过期:
go
func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
// ─── 加写锁:防止过期检查→删除之间被插入新 item ───
c.expirationLock.Lock()
defer c.expirationLock.Unlock()
timestampedItem, exists := c.getTimestampedEntry(key)
if !exists {
return nil, false
}
// ─── 检查是否过期 ───
if c.expirationPolicy.IsExpired(timestampedItem) {
c.cacheStorage.Delete(key) // 过期 → 删除
return nil, false
}
// ─── 未过期 → 返回原始对象(剥离时间戳) ───
return timestampedItem.Obj, true
}
为什么需要 expirationLock?
cacheStorage Add goroutine Get goroutine cacheStorage Add goroutine Get goroutine #mermaid-svg-iFxFz9ahe3NCim1S{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iFxFz9ahe3NCim1S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iFxFz9ahe3NCim1S .error-icon{fill:#552222;}#mermaid-svg-iFxFz9ahe3NCim1S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iFxFz9ahe3NCim1S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iFxFz9ahe3NCim1S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iFxFz9ahe3NCim1S .marker.cross{stroke:#333333;}#mermaid-svg-iFxFz9ahe3NCim1S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iFxFz9ahe3NCim1S p{margin:0;}#mermaid-svg-iFxFz9ahe3NCim1S .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iFxFz9ahe3NCim1S text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-iFxFz9ahe3NCim1S .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iFxFz9ahe3NCim1S .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-iFxFz9ahe3NCim1S .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-iFxFz9ahe3NCim1S .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-iFxFz9ahe3NCim1S #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-iFxFz9ahe3NCim1S .sequenceNumber{fill:white;}#mermaid-svg-iFxFz9ahe3NCim1S #sequencenumber{fill:#333;}#mermaid-svg-iFxFz9ahe3NCim1S #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-iFxFz9ahe3NCim1S .messageText{fill:#333;stroke:none;}#mermaid-svg-iFxFz9ahe3NCim1S .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iFxFz9ahe3NCim1S .labelText,#mermaid-svg-iFxFz9ahe3NCim1S .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-iFxFz9ahe3NCim1S .loopText,#mermaid-svg-iFxFz9ahe3NCim1S .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-iFxFz9ahe3NCim1S .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iFxFz9ahe3NCim1S .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-iFxFz9ahe3NCim1S .noteText,#mermaid-svg-iFxFz9ahe3NCim1S .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-iFxFz9ahe3NCim1S .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iFxFz9ahe3NCim1S .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iFxFz9ahe3NCim1S .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iFxFz9ahe3NCim1S .actorPopupMenu{position:absolute;}#mermaid-svg-iFxFz9ahe3NCim1S .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-iFxFz9ahe3NCim1S .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iFxFz9ahe3NCim1S .actor-man circle,#mermaid-svg-iFxFz9ahe3NCim1S line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-iFxFz9ahe3NCim1S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 无锁场景(错误) 有锁场景(正确) getTimestampedEntry("key") → expiredAdd("key", newObj) ← 新对象插入!Delete("key") ← 把刚插入的新对象也删了!expirationLock.Lock()getTimestampedEntry("key") → expiredexpirationLock.Lock() ← 阻塞等待Delete("key")expirationLock.Unlock()expirationLock.Lock() ← 获得锁Add("key", newObj) ← 安全插入
4.1.3 TTLPolicy
go
type TTLPolicy struct {
TTL time.Duration // >0: 超过 TTL 过期; ≤0: 永不过期
Clock clock.Clock // 可注入 FakeClock
}
func (p *TTLPolicy) IsExpired(obj *TimestampedEntry) bool {
return p.TTL > 0 && p.Clock.Since(obj.Timestamp) > p.TTL
}
4.1.4 TimestampedEntry
go
type TimestampedEntry struct {
Obj interface{} // 原始对象
Timestamp time.Time // 入队时间
key string // 缓存 key
}
Add 操作 :将原始对象包装为 TimestampedEntry 再存入 cacheStorage:
go
func (c *ExpirationCache) Add(obj interface{}) error {
key, err := c.keyFunc(obj)
c.expirationLock.Lock()
defer c.expirationLock.Unlock()
c.cacheStorage.Add(key, &TimestampedEntry{obj, c.clock.Now(), key})
// 时间戳 = 当前时间
return nil
}
Update = Add:更新就是重新添加(刷新时间戳):
go
func (c *ExpirationCache) Update(obj interface{}) error {
return c.Add(obj) // 刷新 Timestamp
}
List:遍历所有 item,过滤过期的:
go
func (c *ExpirationCache) List() []interface{} {
items := c.cacheStorage.List()
list := make([]interface{}, 0, len(items))
for _, item := range items {
key := item.(*TimestampedEntry).key
if obj, exists := c.getOrExpire(key); exists {
list = append(list, obj)
}
// 过期的被自动删除
}
return list
}
4.1.5 构造函数
go
func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
return NewExpirationStore(keyFunc, &TTLPolicy{ttl, clock.RealClock{}})
}
func NewExpirationStore(keyFunc KeyFunc, expirationPolicy ExpirationPolicy) Store {
return &ExpirationCache{
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
// 底层是无索引的 threadSafeMap
keyFunc: keyFunc,
clock: clock.RealClock{},
expirationPolicy: expirationPolicy,
}
}
4.2 Heap --- 线程安全优先队列 (heap.go, 322行)
4.2.1 结构体
go
type Heap struct {
lock sync.RWMutex // 读写锁
cond sync.Cond // 条件变量(Pop 阻塞等待)
data *heapData // 堆数据
closed bool // 关闭标记
}
type heapData struct {
items map[string]*heapItem // key → item 映射
queue []string // 最小堆(key 列表)
keyFunc KeyFunc // key 提取函数
lessFunc LessFunc // 比较函数
}
type heapItem struct {
obj interface{} // 存储的对象
index int // 在 queue 中的位置(供 heap.Fix 使用)
}
4.2.2 Add --- 插入/更新
go
func (h *Heap) Add(obj interface{}) error {
key, err := h.data.keyFunc(obj)
h.lock.Lock()
defer h.lock.Unlock()
if h.closed {
return fmt.Errorf(closedMsg)
}
if _, exists := h.data.items[key]; exists {
// 已存在 → 更新对象 + 调整堆
h.data.items[key].obj = obj
heap.Fix(h.data, h.data.items[key].index)
// O(log n) 重新调整
} else {
// 不存在 → 新插入
h.addIfNotPresentLocked(key, obj)
}
h.cond.Broadcast() // 唤醒 Pop
return nil
}
4.2.3 Pop --- 阻塞弹出
go
func (h *Heap) Pop() (interface{}, error) {
h.lock.Lock()
defer h.lock.Unlock()
for len(h.data.queue) == 0 {
if h.closed {
return nil, fmt.Errorf("heap is closed")
}
h.cond.Wait() // 等待 Add 或 Close
}
obj := heap.Pop(h.data) // 弹出堆顶(最小元素)
return obj, nil
}
4.2.4 Delete --- 删除
go
func (h *Heap) Delete(obj interface{}) error {
key, err := h.data.keyFunc(obj)
h.lock.Lock()
defer h.lock.Unlock()
if item, ok := h.data.items[key]; ok {
heap.Remove(h.data, item.index)
// O(log n) 从堆中移除
return nil
}
return fmt.Errorf("object not found")
}
Heap 操作与堆调整:
#mermaid-svg-LoU1ljVE37drZecu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LoU1ljVE37drZecu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LoU1ljVE37drZecu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LoU1ljVE37drZecu .error-icon{fill:#552222;}#mermaid-svg-LoU1ljVE37drZecu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LoU1ljVE37drZecu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LoU1ljVE37drZecu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LoU1ljVE37drZecu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LoU1ljVE37drZecu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LoU1ljVE37drZecu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LoU1ljVE37drZecu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LoU1ljVE37drZecu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LoU1ljVE37drZecu .marker.cross{stroke:#333333;}#mermaid-svg-LoU1ljVE37drZecu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LoU1ljVE37drZecu p{margin:0;}#mermaid-svg-LoU1ljVE37drZecu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LoU1ljVE37drZecu .cluster-label text{fill:#333;}#mermaid-svg-LoU1ljVE37drZecu .cluster-label span{color:#333;}#mermaid-svg-LoU1ljVE37drZecu .cluster-label span p{background-color:transparent;}#mermaid-svg-LoU1ljVE37drZecu .label text,#mermaid-svg-LoU1ljVE37drZecu span{fill:#333;color:#333;}#mermaid-svg-LoU1ljVE37drZecu .node rect,#mermaid-svg-LoU1ljVE37drZecu .node circle,#mermaid-svg-LoU1ljVE37drZecu .node ellipse,#mermaid-svg-LoU1ljVE37drZecu .node polygon,#mermaid-svg-LoU1ljVE37drZecu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LoU1ljVE37drZecu .rough-node .label text,#mermaid-svg-LoU1ljVE37drZecu .node .label text,#mermaid-svg-LoU1ljVE37drZecu .image-shape .label,#mermaid-svg-LoU1ljVE37drZecu .icon-shape .label{text-anchor:middle;}#mermaid-svg-LoU1ljVE37drZecu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LoU1ljVE37drZecu .rough-node .label,#mermaid-svg-LoU1ljVE37drZecu .node .label,#mermaid-svg-LoU1ljVE37drZecu .image-shape .label,#mermaid-svg-LoU1ljVE37drZecu .icon-shape .label{text-align:center;}#mermaid-svg-LoU1ljVE37drZecu .node.clickable{cursor:pointer;}#mermaid-svg-LoU1ljVE37drZecu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LoU1ljVE37drZecu .arrowheadPath{fill:#333333;}#mermaid-svg-LoU1ljVE37drZecu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LoU1ljVE37drZecu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LoU1ljVE37drZecu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LoU1ljVE37drZecu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LoU1ljVE37drZecu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LoU1ljVE37drZecu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LoU1ljVE37drZecu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LoU1ljVE37drZecu .cluster text{fill:#333;}#mermaid-svg-LoU1ljVE37drZecu .cluster span{color:#333;}#mermaid-svg-LoU1ljVE37drZecu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LoU1ljVE37drZecu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LoU1ljVE37drZecu rect.text{fill:none;stroke-width:0;}#mermaid-svg-LoU1ljVE37drZecu .icon-shape,#mermaid-svg-LoU1ljVE37drZecu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LoU1ljVE37drZecu .icon-shape p,#mermaid-svg-LoU1ljVE37drZecu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LoU1ljVE37drZecu .icon-shape .label rect,#mermaid-svg-LoU1ljVE37drZecu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LoU1ljVE37drZecu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LoU1ljVE37drZecu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LoU1ljVE37drZecu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Heap 操作
Add(obj)
Pop()
Delete(obj)
itemskey 存在?
heap.Fix()
O(log n) 重新调整
heap.Push()
O(log n) 插入
heap.Pop()
O(log n) 弹出堆顶
heap.Remove()
O(log n) 删除指定元素
4.3 MutationCache --- 变更叠加 LRU 缓存 (mutation_cache.go, 264行)
4.3.1 设计意图
MutationCache 解决的核心问题:Informer 缓存可能滞后于最近写入。
场景:Controller 更新了一个对象,然后立即通过 Lister 查询。但 Informer 的 Watch 事件可能还没到达,导致读到旧数据。MutationCache 在 Store 之上叠加一个 LRU 层,缓存最近的 Mutation,使 Get 返回最新版本。
#mermaid-svg-8sYhJzgcdSYVGokR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8sYhJzgcdSYVGokR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8sYhJzgcdSYVGokR .error-icon{fill:#552222;}#mermaid-svg-8sYhJzgcdSYVGokR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8sYhJzgcdSYVGokR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8sYhJzgcdSYVGokR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8sYhJzgcdSYVGokR .marker.cross{stroke:#333333;}#mermaid-svg-8sYhJzgcdSYVGokR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8sYhJzgcdSYVGokR p{margin:0;}#mermaid-svg-8sYhJzgcdSYVGokR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8sYhJzgcdSYVGokR .cluster-label text{fill:#333;}#mermaid-svg-8sYhJzgcdSYVGokR .cluster-label span{color:#333;}#mermaid-svg-8sYhJzgcdSYVGokR .cluster-label span p{background-color:transparent;}#mermaid-svg-8sYhJzgcdSYVGokR .label text,#mermaid-svg-8sYhJzgcdSYVGokR span{fill:#333;color:#333;}#mermaid-svg-8sYhJzgcdSYVGokR .node rect,#mermaid-svg-8sYhJzgcdSYVGokR .node circle,#mermaid-svg-8sYhJzgcdSYVGokR .node ellipse,#mermaid-svg-8sYhJzgcdSYVGokR .node polygon,#mermaid-svg-8sYhJzgcdSYVGokR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8sYhJzgcdSYVGokR .rough-node .label text,#mermaid-svg-8sYhJzgcdSYVGokR .node .label text,#mermaid-svg-8sYhJzgcdSYVGokR .image-shape .label,#mermaid-svg-8sYhJzgcdSYVGokR .icon-shape .label{text-anchor:middle;}#mermaid-svg-8sYhJzgcdSYVGokR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8sYhJzgcdSYVGokR .rough-node .label,#mermaid-svg-8sYhJzgcdSYVGokR .node .label,#mermaid-svg-8sYhJzgcdSYVGokR .image-shape .label,#mermaid-svg-8sYhJzgcdSYVGokR .icon-shape .label{text-align:center;}#mermaid-svg-8sYhJzgcdSYVGokR .node.clickable{cursor:pointer;}#mermaid-svg-8sYhJzgcdSYVGokR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8sYhJzgcdSYVGokR .arrowheadPath{fill:#333333;}#mermaid-svg-8sYhJzgcdSYVGokR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8sYhJzgcdSYVGokR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8sYhJzgcdSYVGokR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8sYhJzgcdSYVGokR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8sYhJzgcdSYVGokR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8sYhJzgcdSYVGokR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8sYhJzgcdSYVGokR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8sYhJzgcdSYVGokR .cluster text{fill:#333;}#mermaid-svg-8sYhJzgcdSYVGokR .cluster span{color:#333;}#mermaid-svg-8sYhJzgcdSYVGokR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8sYhJzgcdSYVGokR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8sYhJzgcdSYVGokR rect.text{fill:none;stroke-width:0;}#mermaid-svg-8sYhJzgcdSYVGokR .icon-shape,#mermaid-svg-8sYhJzgcdSYVGokR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8sYhJzgcdSYVGokR .icon-shape p,#mermaid-svg-8sYhJzgcdSYVGokR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8sYhJzgcdSYVGokR .icon-shape .label rect,#mermaid-svg-8sYhJzgcdSYVGokR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8sYhJzgcdSYVGokR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8sYhJzgcdSYVGokR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8sYhJzgcdSYVGokR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 返回更新版本
Controller 写入
Update(obj)
MutationCache.Mutation(obj)
LRU Expire Cache
(最近写入)
Controller 读取
Lister.Get()
MutationCache.GetByKey()
Backing Store
(Informer 缓存)
CompareResourceVersion
取更新版本
4.3.2 GetByKey --- 双层取新
go
func (c *mutationCache) GetByKey(key string) (interface{}, bool, error) {
c.lock.Lock()
defer c.lock.Unlock()
// 1. 先查 backingCache(Informer 缓存)
obj, exists, err := c.backingCache.GetByKey(key)
if err != nil {
return nil, false, err
}
if !exists {
if !c.includeAdds {
// backingCache 中没有 → 返回不存在
// 原因:无法区分"未观察到创建"和"创建后被删除"
return nil, false, nil
}
// includeAdds=true → 检查 mutationCache
obj, exists = c.mutationCache.Get(key)
if !exists {
return nil, false, nil
}
}
// 2. 如果是 runtime.Object → 取更新版本
objRuntime, ok := obj.(runtime.Object)
if !ok {
return obj, true, nil
}
return c.newerObject(key, objRuntime), true, nil
}
4.3.3 newerObject --- 版本比较
go
func (c *mutationCache) newerObject(key string, backing runtime.Object) runtime.Object {
mutatedObj, exists := c.mutationCache.Get(key)
if !exists {
return backing // LRU 中没有 → 返回 backing
}
mutatedObjRuntime, ok := mutatedObj.(runtime.Object)
if !ok {
return backing
}
// 比较 ResourceVersion:backing >= mutated → 移除 LRU 条目
if c.comparator.CompareResourceVersion(backing, mutatedObjRuntime) >= 0 {
c.mutationCache.Remove(key)
return backing // backing 已更新 → 用 backing
}
return mutatedObjRuntime // LRU 更新 → 用 LRU
}
4.3.4 etcdObjectVersioner --- ResourceVersion 比较
go
type etcdObjectVersioner struct{}
func (a etcdObjectVersioner) CompareResourceVersion(lhs, rhs runtime.Object) int {
lhsVersion, _ := a.ObjectResourceVersion(lhs) // 解析为 uint64
rhsVersion, _ := a.ObjectResourceVersion(rhs)
if lhsVersion == rhsVersion { return 0 }
if lhsVersion < rhsVersion { return -1 }
return 1
}
4.4 UndeltaStore --- 全量推送 Store (undelta_store.go, 89行)
4.4.1 设计意图
UndeltaStore 实现了增量转全量的语义。每次 Store 发生任何变更(Add/Update/Delete/Replace),都调用 PushFunc 推送完整状态列表。
适用场景:某些消费者只关心完整状态快照,不关心增量变化。
go
type UndeltaStore struct {
Store // 嵌入标准 Store
PushFunc func([]interface{}) // 全量推送回调
}
4.4.2 核心操作
go
func (u *UndeltaStore) Add(obj interface{}) error {
if err := u.Store.Add(obj); err != nil { return err }
u.PushFunc(u.Store.List()) // 推送完整列表
return nil
}
func (u *UndeltaStore) Update(obj interface{}) error {
if err := u.Store.Update(obj); err != nil { return err }
u.PushFunc(u.Store.List()) // 推送完整列表
return nil
}
func (u *UndeltaStore) Delete(obj interface{}) error {
if err := u.Store.Delete(obj); err != nil { return err }
u.PushFunc(u.Store.List()) // 推送完整列表
return nil
}
func (u *UndeltaStore) Replace(list []interface{}, rv string) error {
if err := u.Store.Replace(list, rv); err != nil { return err }
u.PushFunc(u.Store.List()) // 推送完整列表
return nil
}
注意:由于 Store.Add 和 PushFunc 之间锁会释放,可能出现两次 PushFunc 传入相同列表的情况(竞态但无害)。
4.5 MutationDetector --- 缓存变更检测 (mutation_detector.go, 167行)
4.5.1 设计意图
检测 Informer 缓存中的对象是否被非法修改(深拷贝对比)。这对调试非常有用:Informer 的对象应该是只读的,如果用户代码直接修改了缓存对象,会导致数据不一致。
默认关闭 ,通过环境变量 KUBE_CACHE_MUTATION_DETECTOR=true 启用。
go
var mutationDetectionEnabled = false
func init() {
mutationDetectionEnabled, _ = strconv.ParseBool(os.Getenv("KUBE_CACHE_MUTATION_DETECTOR"))
}
4.5.2 defaultCacheMutationDetector
go
type defaultCacheMutationDetector struct {
name string
period time.Duration // 检查周期(1s)
compareObjectsLock sync.Mutex
addedObjsLock sync.Mutex
addedObjs []cacheObj // 新添加的对象(待移动到 cachedObjs)
cachedObjs []cacheObj // 当前监控的对象列表
retainedCachedObjs []cacheObj // 保留期内的旧对象
retainDuration time.Duration // 保留时长(2min)
lastRotated time.Time
failureFunc func(string) // 可注入的失败回调
}
type cacheObj struct {
cached interface{} // 原始对象(指向缓存中的引用)
copied interface{} // 深拷贝(用于对比)
}
4.5.3 工作流程
#mermaid-svg-FXAvxfoUfNfxEYg4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FXAvxfoUfNfxEYg4 .error-icon{fill:#552222;}#mermaid-svg-FXAvxfoUfNfxEYg4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FXAvxfoUfNfxEYg4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .marker.cross{stroke:#333333;}#mermaid-svg-FXAvxfoUfNfxEYg4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FXAvxfoUfNfxEYg4 p{margin:0;}#mermaid-svg-FXAvxfoUfNfxEYg4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .cluster-label text{fill:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .cluster-label span{color:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .cluster-label span p{background-color:transparent;}#mermaid-svg-FXAvxfoUfNfxEYg4 .label text,#mermaid-svg-FXAvxfoUfNfxEYg4 span{fill:#333;color:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .node rect,#mermaid-svg-FXAvxfoUfNfxEYg4 .node circle,#mermaid-svg-FXAvxfoUfNfxEYg4 .node ellipse,#mermaid-svg-FXAvxfoUfNfxEYg4 .node polygon,#mermaid-svg-FXAvxfoUfNfxEYg4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .rough-node .label text,#mermaid-svg-FXAvxfoUfNfxEYg4 .node .label text,#mermaid-svg-FXAvxfoUfNfxEYg4 .image-shape .label,#mermaid-svg-FXAvxfoUfNfxEYg4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-FXAvxfoUfNfxEYg4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .rough-node .label,#mermaid-svg-FXAvxfoUfNfxEYg4 .node .label,#mermaid-svg-FXAvxfoUfNfxEYg4 .image-shape .label,#mermaid-svg-FXAvxfoUfNfxEYg4 .icon-shape .label{text-align:center;}#mermaid-svg-FXAvxfoUfNfxEYg4 .node.clickable{cursor:pointer;}#mermaid-svg-FXAvxfoUfNfxEYg4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .arrowheadPath{fill:#333333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FXAvxfoUfNfxEYg4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FXAvxfoUfNfxEYg4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FXAvxfoUfNfxEYg4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FXAvxfoUfNfxEYg4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .cluster text{fill:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 .cluster span{color:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FXAvxfoUfNfxEYg4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FXAvxfoUfNfxEYg4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-FXAvxfoUfNfxEYg4 .icon-shape,#mermaid-svg-FXAvxfoUfNfxEYg4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FXAvxfoUfNfxEYg4 .icon-shape p,#mermaid-svg-FXAvxfoUfNfxEYg4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FXAvxfoUfNfxEYg4 .icon-shape .label rect,#mermaid-svg-FXAvxfoUfNfxEYg4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FXAvxfoUfNfxEYg4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FXAvxfoUfNfxEYg4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FXAvxfoUfNfxEYg4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
不同
相同
AddObject(obj)
obj.DeepCopyObject()
存储: {cached: obj, copied: copy}
Run(stopCh)
超过 retainDuration?
cachedObjs → retainedCachedObjs
cachedObjs = nil
CompareObjects()
移动 addedObjs → cachedObjs
reflect.DeepEqual
(cached, copied)?
CACHE ALTERED!
panic / failureFunc
等待 period
关键设计 :检测到修改后 panic,因为这是 P0 bug------缓存被修改后所有后续计算都不可信。
4.6 ListWatch --- 列表监视器 (listwatch.go, 312行)
4.6.1 接口层次
#mermaid-svg-QPbMHDQn0i4o4cH3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QPbMHDQn0i4o4cH3 .error-icon{fill:#552222;}#mermaid-svg-QPbMHDQn0i4o4cH3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QPbMHDQn0i4o4cH3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QPbMHDQn0i4o4cH3 .marker.cross{stroke:#333333;}#mermaid-svg-QPbMHDQn0i4o4cH3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QPbMHDQn0i4o4cH3 p{margin:0;}#mermaid-svg-QPbMHDQn0i4o4cH3 g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-QPbMHDQn0i4o4cH3 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-QPbMHDQn0i4o4cH3 .cluster-label text{fill:#333;}#mermaid-svg-QPbMHDQn0i4o4cH3 .cluster-label span{color:#333;}#mermaid-svg-QPbMHDQn0i4o4cH3 .cluster-label span p{background-color:transparent;}#mermaid-svg-QPbMHDQn0i4o4cH3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QPbMHDQn0i4o4cH3 .cluster text{fill:#333;}#mermaid-svg-QPbMHDQn0i4o4cH3 .cluster span{color:#333;}#mermaid-svg-QPbMHDQn0i4o4cH3 .nodeLabel,#mermaid-svg-QPbMHDQn0i4o4cH3 .edgeLabel{color:#131300;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-QPbMHDQn0i4o4cH3 .label text{fill:#131300;}#mermaid-svg-QPbMHDQn0i4o4cH3 .labelBkg{background:#ECECFF;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-QPbMHDQn0i4o4cH3 .classTitle{font-weight:bolder;}#mermaid-svg-QPbMHDQn0i4o4cH3 .node rect,#mermaid-svg-QPbMHDQn0i4o4cH3 .node circle,#mermaid-svg-QPbMHDQn0i4o4cH3 .node ellipse,#mermaid-svg-QPbMHDQn0i4o4cH3 .node polygon,#mermaid-svg-QPbMHDQn0i4o4cH3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QPbMHDQn0i4o4cH3 .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 g.clickable{cursor:pointer;}#mermaid-svg-QPbMHDQn0i4o4cH3 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-QPbMHDQn0i4o4cH3 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-QPbMHDQn0i4o4cH3 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-QPbMHDQn0i4o4cH3 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-QPbMHDQn0i4o4cH3 .dashed-line{stroke-dasharray:3;}#mermaid-svg-QPbMHDQn0i4o4cH3 .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-QPbMHDQn0i4o4cH3 #compositionStart,#mermaid-svg-QPbMHDQn0i4o4cH3 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #compositionEnd,#mermaid-svg-QPbMHDQn0i4o4cH3 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #dependencyStart,#mermaid-svg-QPbMHDQn0i4o4cH3 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #dependencyStart,#mermaid-svg-QPbMHDQn0i4o4cH3 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #extensionStart,#mermaid-svg-QPbMHDQn0i4o4cH3 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #extensionEnd,#mermaid-svg-QPbMHDQn0i4o4cH3 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #aggregationStart,#mermaid-svg-QPbMHDQn0i4o4cH3 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #aggregationEnd,#mermaid-svg-QPbMHDQn0i4o4cH3 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #lollipopStart,#mermaid-svg-QPbMHDQn0i4o4cH3 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 #lollipopEnd,#mermaid-svg-QPbMHDQn0i4o4cH3 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-QPbMHDQn0i4o4cH3 .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-QPbMHDQn0i4o4cH3 .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QPbMHDQn0i4o4cH3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QPbMHDQn0i4o4cH3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QPbMHDQn0i4o4cH3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} <<interface>>
Lister
+List(options)(runtime.Object, error)
<<interface>>
ListerWithContext
+ListWithContext(ctx, options)(runtime.Object, error)
<<interface>>
Watcher
+Watch(options)(watch.Interface, error)
<<interface>>
WatcherWithContext
+WatchWithContext(ctx, options)(watch.Interface, error)
<<interface>>
ListerWatcher
+List(options)(runtime.Object, error)
+Watch(options)(watch.Interface, error)
<<interface>>
ListerWatcherWithContext
+ListWithContext(ctx, options)(runtime.Object, error)
+WatchWithContext(ctx, options)(watch.Interface, error)
ListWatch
+ListFunc ListFunc
+WatchFunc WatchFunc
+ListWithContextFunc ListWithContextFunc
+WatchFuncWithContext WatchFuncWithContext
+DisableChunking bool
4.6.2 ListWatch 结构体
go
type ListWatch struct {
ListFunc ListFunc // Deprecated
WatchFunc WatchFunc // Deprecated
ListWithContextFunc ListWithContextFunc // 推荐
WatchFuncWithContext WatchFuncWithContext // 推荐
DisableChunking bool // 禁用分页
}
优先级:WithContext 版本优先,旧版本作为 fallback。
go
func (lw *ListWatch) ListWithContext(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) {
if lw.ListWithContextFunc != nil {
return lw.ListWithContextFunc(ctx, options)
}
return lw.ListFunc(options) // fallback
}
4.6.3 WatchList 语义适配
go
type listWatcherWithWatchListSemanticsWrapper struct {
*ListWatch
unsupportedWatchListSemantics bool
// true = 该客户端不支持 WatchList 语义
// Reflector 检测到此标记后,即使 WatchList feature gate 开启
// 也会使用传统的 List + Watch 模式
}
4.7 GenericLister --- 通用列表查询 (listers.go, 184行)
4.7.1 接口
go
type GenericLister interface {
List(selector labels.Selector) ([]runtime.Object, error)
Get(name string) (runtime.Object, error)
ByNamespace(namespace string) GenericNamespaceLister
}
type GenericNamespaceLister interface {
List(selector labels.Selector) ([]runtime.Object, error)
Get(name string) (runtime.Object, error)
}
4.7.2 ListAllByNamespace --- 核心查询函数
go
func ListAllByNamespace(indexer Indexer, namespace string, selector labels.Selector, appendFn AppendFunc) error {
if labels.MatchesNothing(selector) {
return nil // 空选择器 → 无结果
}
if namespace == metav1.NamespaceAll {
return ListAll(indexer, selector, appendFn)
// 全命名空间 → 遍历所有对象
}
// 使用 NamespaceIndex 加速
items, err := indexer.Index(NamespaceIndex, &metav1.ObjectMeta{Namespace: namespace})
if err != nil {
// 索引失败 → 退化为全量扫描
for _, m := range indexer.List() {
metadata, _ := meta.Accessor(m)
if metadata.GetNamespace() == namespace && selector.Matches(labels.Set(metadata.GetLabels())) {
appendFn(m)
}
}
return nil
}
// 在索引结果上做 label 选择
selectAll := selector.Empty()
for _, m := range items {
if selectAll {
appendFn(m) // 空选择器 → 全选(跳过 label 计算)
} else if selector.Matches(labels.Set(metadata.GetLabels())) {
appendFn(m)
}
}
return nil
}
查询路径决策:
#mermaid-svg-gzwZQJdUSsXQ67B8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gzwZQJdUSsXQ67B8 .error-icon{fill:#552222;}#mermaid-svg-gzwZQJdUSsXQ67B8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gzwZQJdUSsXQ67B8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .marker.cross{stroke:#333333;}#mermaid-svg-gzwZQJdUSsXQ67B8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gzwZQJdUSsXQ67B8 p{margin:0;}#mermaid-svg-gzwZQJdUSsXQ67B8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .cluster-label text{fill:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .cluster-label span{color:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .cluster-label span p{background-color:transparent;}#mermaid-svg-gzwZQJdUSsXQ67B8 .label text,#mermaid-svg-gzwZQJdUSsXQ67B8 span{fill:#333;color:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .node rect,#mermaid-svg-gzwZQJdUSsXQ67B8 .node circle,#mermaid-svg-gzwZQJdUSsXQ67B8 .node ellipse,#mermaid-svg-gzwZQJdUSsXQ67B8 .node polygon,#mermaid-svg-gzwZQJdUSsXQ67B8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .rough-node .label text,#mermaid-svg-gzwZQJdUSsXQ67B8 .node .label text,#mermaid-svg-gzwZQJdUSsXQ67B8 .image-shape .label,#mermaid-svg-gzwZQJdUSsXQ67B8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-gzwZQJdUSsXQ67B8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .rough-node .label,#mermaid-svg-gzwZQJdUSsXQ67B8 .node .label,#mermaid-svg-gzwZQJdUSsXQ67B8 .image-shape .label,#mermaid-svg-gzwZQJdUSsXQ67B8 .icon-shape .label{text-align:center;}#mermaid-svg-gzwZQJdUSsXQ67B8 .node.clickable{cursor:pointer;}#mermaid-svg-gzwZQJdUSsXQ67B8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .arrowheadPath{fill:#333333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gzwZQJdUSsXQ67B8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gzwZQJdUSsXQ67B8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gzwZQJdUSsXQ67B8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gzwZQJdUSsXQ67B8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .cluster text{fill:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 .cluster span{color:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gzwZQJdUSsXQ67B8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gzwZQJdUSsXQ67B8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-gzwZQJdUSsXQ67B8 .icon-shape,#mermaid-svg-gzwZQJdUSsXQ67B8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gzwZQJdUSsXQ67B8 .icon-shape p,#mermaid-svg-gzwZQJdUSsXQ67B8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gzwZQJdUSsXQ67B8 .icon-shape .label rect,#mermaid-svg-gzwZQJdUSsXQ67B8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gzwZQJdUSsXQ67B8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gzwZQJdUSsXQ67B8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gzwZQJdUSsXQ67B8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Yes
No
No
Yes
Yes
No
ListAllByNamespace
selector 匹配空集?
namespace == NamespaceAll?
indexer.Index(NamespaceIndex)
索引成功?
全量扫描 + namespace 过滤
selector.Empty()?
直接 append(跳过 label 计算)
selector.Matches(labels)
索引结果 + label 过滤
return nil
ListAll()
4.8 InformerName --- 全局命名注册 (identity.go, 217行)
4.8.1 设计意图
为 Informer 提供全局唯一的名称标识,用于指标和日志。同一进程中,同一 name+GVR 组合只能有一个 Informer 注册指标,防止重复计数。
#mermaid-svg-CV5194W8NPkPBTmq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CV5194W8NPkPBTmq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CV5194W8NPkPBTmq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CV5194W8NPkPBTmq .error-icon{fill:#552222;}#mermaid-svg-CV5194W8NPkPBTmq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CV5194W8NPkPBTmq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CV5194W8NPkPBTmq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CV5194W8NPkPBTmq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CV5194W8NPkPBTmq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CV5194W8NPkPBTmq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CV5194W8NPkPBTmq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CV5194W8NPkPBTmq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CV5194W8NPkPBTmq .marker.cross{stroke:#333333;}#mermaid-svg-CV5194W8NPkPBTmq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CV5194W8NPkPBTmq p{margin:0;}#mermaid-svg-CV5194W8NPkPBTmq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CV5194W8NPkPBTmq .cluster-label text{fill:#333;}#mermaid-svg-CV5194W8NPkPBTmq .cluster-label span{color:#333;}#mermaid-svg-CV5194W8NPkPBTmq .cluster-label span p{background-color:transparent;}#mermaid-svg-CV5194W8NPkPBTmq .label text,#mermaid-svg-CV5194W8NPkPBTmq span{fill:#333;color:#333;}#mermaid-svg-CV5194W8NPkPBTmq .node rect,#mermaid-svg-CV5194W8NPkPBTmq .node circle,#mermaid-svg-CV5194W8NPkPBTmq .node ellipse,#mermaid-svg-CV5194W8NPkPBTmq .node polygon,#mermaid-svg-CV5194W8NPkPBTmq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CV5194W8NPkPBTmq .rough-node .label text,#mermaid-svg-CV5194W8NPkPBTmq .node .label text,#mermaid-svg-CV5194W8NPkPBTmq .image-shape .label,#mermaid-svg-CV5194W8NPkPBTmq .icon-shape .label{text-anchor:middle;}#mermaid-svg-CV5194W8NPkPBTmq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CV5194W8NPkPBTmq .rough-node .label,#mermaid-svg-CV5194W8NPkPBTmq .node .label,#mermaid-svg-CV5194W8NPkPBTmq .image-shape .label,#mermaid-svg-CV5194W8NPkPBTmq .icon-shape .label{text-align:center;}#mermaid-svg-CV5194W8NPkPBTmq .node.clickable{cursor:pointer;}#mermaid-svg-CV5194W8NPkPBTmq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CV5194W8NPkPBTmq .arrowheadPath{fill:#333333;}#mermaid-svg-CV5194W8NPkPBTmq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CV5194W8NPkPBTmq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CV5194W8NPkPBTmq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CV5194W8NPkPBTmq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CV5194W8NPkPBTmq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CV5194W8NPkPBTmq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CV5194W8NPkPBTmq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CV5194W8NPkPBTmq .cluster text{fill:#333;}#mermaid-svg-CV5194W8NPkPBTmq .cluster span{color:#333;}#mermaid-svg-CV5194W8NPkPBTmq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CV5194W8NPkPBTmq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CV5194W8NPkPBTmq rect.text{fill:none;stroke-width:0;}#mermaid-svg-CV5194W8NPkPBTmq .icon-shape,#mermaid-svg-CV5194W8NPkPBTmq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CV5194W8NPkPBTmq .icon-shape p,#mermaid-svg-CV5194W8NPkPBTmq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CV5194W8NPkPBTmq .icon-shape .label rect,#mermaid-svg-CV5194W8NPkPBTmq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CV5194W8NPkPBTmq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CV5194W8NPkPBTmq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CV5194W8NPkPBTmq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NewInformerName('kube-controller-manager')
全局注册表
informerNameRegistry.names
.WithResource(pods)
.WithResource(services)
InformerNameAndResource
name='kcm', gvr='pods'
reserved=true
InformerNameAndResource
name='kcm', gvr='services'
reserved=true
.WithResource(pods) 第二次
InformerNameAndResource
reserved=false
(重复 GVR)
4.8.2 WithResource --- GVR 级别去重
go
func (n *InformerName) WithResource(gvr schema.GroupVersionResource) InformerNameAndResource {
n.lock.Lock()
defer n.lock.Unlock()
retval := InformerNameAndResource{name: n.name, gvr: gvr, reserved: &atomic.Bool{}}
if n.reserved.Load() {
if _, gvrExists := n.gvrs[gvr]; !gvrExists {
// 首次注册此 GVR → reserved=true,启用指标
retval.reserved.Store(true)
n.gvrs[gvr] = retval.reserved
} else {
// 重复注册同一 GVR → reserved=false,禁用指标
klog.TODO().Error(nil, "Duplicate informer registration")
}
}
return retval
}
4.8.3 Reserved --- 热路径无锁检查
go
func (n InformerNameAndResource) Reserved() bool {
if n.reserved == nil {
return false
}
return n.reserved.Load()
// atomic load → 无锁,适合热路径(每次队列操作都调用)
}
4.9 synctrack --- 同步状态追踪 (synctrack/synctrack.go + lazy.go, 297行)
4.9.1 AsyncTrackerT --- 多 Worker 异步追踪
go
type AsyncTracker[T comparable] struct {
name string
upstreamHasSynced atomic.Bool // 上游是否已同步
lock sync.Mutex
waiting sets.Set[T] // 等待处理的 key 集合
synced context.Context // 同步完成后取消
cancel func()
}
工作流程:
Start(key)--- 添加 key 到 waiting 集合Finished(key)--- 从 waiting 移除 key,检查是否全部完成UpstreamHasSynced()--- 标记上游已同步HasSynced()--- 返回synced.Err() != nil(context 被取消 = 已同步)
#mermaid-svg-Ce9UMv5P336J7qnH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ce9UMv5P336J7qnH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ce9UMv5P336J7qnH .error-icon{fill:#552222;}#mermaid-svg-Ce9UMv5P336J7qnH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ce9UMv5P336J7qnH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ce9UMv5P336J7qnH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ce9UMv5P336J7qnH .marker.cross{stroke:#333333;}#mermaid-svg-Ce9UMv5P336J7qnH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ce9UMv5P336J7qnH p{margin:0;}#mermaid-svg-Ce9UMv5P336J7qnH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ce9UMv5P336J7qnH .cluster-label text{fill:#333;}#mermaid-svg-Ce9UMv5P336J7qnH .cluster-label span{color:#333;}#mermaid-svg-Ce9UMv5P336J7qnH .cluster-label span p{background-color:transparent;}#mermaid-svg-Ce9UMv5P336J7qnH .label text,#mermaid-svg-Ce9UMv5P336J7qnH span{fill:#333;color:#333;}#mermaid-svg-Ce9UMv5P336J7qnH .node rect,#mermaid-svg-Ce9UMv5P336J7qnH .node circle,#mermaid-svg-Ce9UMv5P336J7qnH .node ellipse,#mermaid-svg-Ce9UMv5P336J7qnH .node polygon,#mermaid-svg-Ce9UMv5P336J7qnH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ce9UMv5P336J7qnH .rough-node .label text,#mermaid-svg-Ce9UMv5P336J7qnH .node .label text,#mermaid-svg-Ce9UMv5P336J7qnH .image-shape .label,#mermaid-svg-Ce9UMv5P336J7qnH .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ce9UMv5P336J7qnH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ce9UMv5P336J7qnH .rough-node .label,#mermaid-svg-Ce9UMv5P336J7qnH .node .label,#mermaid-svg-Ce9UMv5P336J7qnH .image-shape .label,#mermaid-svg-Ce9UMv5P336J7qnH .icon-shape .label{text-align:center;}#mermaid-svg-Ce9UMv5P336J7qnH .node.clickable{cursor:pointer;}#mermaid-svg-Ce9UMv5P336J7qnH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ce9UMv5P336J7qnH .arrowheadPath{fill:#333333;}#mermaid-svg-Ce9UMv5P336J7qnH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ce9UMv5P336J7qnH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ce9UMv5P336J7qnH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ce9UMv5P336J7qnH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ce9UMv5P336J7qnH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ce9UMv5P336J7qnH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ce9UMv5P336J7qnH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ce9UMv5P336J7qnH .cluster text{fill:#333;}#mermaid-svg-Ce9UMv5P336J7qnH .cluster span{color:#333;}#mermaid-svg-Ce9UMv5P336J7qnH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ce9UMv5P336J7qnH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ce9UMv5P336J7qnH rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ce9UMv5P336J7qnH .icon-shape,#mermaid-svg-Ce9UMv5P336J7qnH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ce9UMv5P336J7qnH .icon-shape p,#mermaid-svg-Ce9UMv5P336J7qnH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ce9UMv5P336J7qnH .icon-shape .label rect,#mermaid-svg-Ce9UMv5P336J7qnH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ce9UMv5P336J7qnH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ce9UMv5P336J7qnH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ce9UMv5P336J7qnH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
Start(keyA)
Start(keyB)
Start(keyC)
waiting = {A, B, C}
Finished(keyA)
Finished(keyB)
Finished(keyC)
UpstreamHasSynced()
waiting 为空 &&
upstreamHasSynced?
cancel() → HasSynced()=true
4.9.2 SingleFileTracker --- 单线程追踪
go
type SingleFileTracker struct {
name string
count int64 // 原子计数器
upstreamHasSynced atomic.Bool
synced context.Context
cancel func()
}
与 AsyncTracker 的区别:不跟踪具体 key,只跟踪计数。适用于事件按序处理的场景(如队列消费者)。
go
func (t *SingleFileTracker) Start() {
atomic.AddInt64(&t.count, 1) // 计数+1
}
func (t *SingleFileTracker) Finished() {
result := atomic.AddInt64(&t.count, -1) // 计数-1
if result < 0 {
panic("negative counter") // 逻辑错误检测
}
if result == 0 && t.upstreamHasSynced.Load() {
t.cancel() // 全部完成 + 上游已同步 → 标记完成
}
}
4.9.3 LazyT --- 延迟求值缓存
go
type Lazy[T any] struct {
Evaluate func() (T, error) // 求值函数
cache atomic.Pointer[cacheEntry[T]] // 缓存条目
}
type cacheEntry[T any] struct {
eval func() (T, error)
lock sync.RWMutex
result *T // nil = 未求值
}
工作流程:
cacheEntry LazyT 调用方 cacheEntry LazyT 调用方 #mermaid-svg-IpaOkD1bFSzh9pUL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IpaOkD1bFSzh9pUL .error-icon{fill:#552222;}#mermaid-svg-IpaOkD1bFSzh9pUL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IpaOkD1bFSzh9pUL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IpaOkD1bFSzh9pUL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IpaOkD1bFSzh9pUL .marker.cross{stroke:#333333;}#mermaid-svg-IpaOkD1bFSzh9pUL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IpaOkD1bFSzh9pUL p{margin:0;}#mermaid-svg-IpaOkD1bFSzh9pUL .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IpaOkD1bFSzh9pUL text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IpaOkD1bFSzh9pUL .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IpaOkD1bFSzh9pUL .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IpaOkD1bFSzh9pUL #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IpaOkD1bFSzh9pUL .sequenceNumber{fill:white;}#mermaid-svg-IpaOkD1bFSzh9pUL #sequencenumber{fill:#333;}#mermaid-svg-IpaOkD1bFSzh9pUL #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IpaOkD1bFSzh9pUL .messageText{fill:#333;stroke:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IpaOkD1bFSzh9pUL .labelText,#mermaid-svg-IpaOkD1bFSzh9pUL .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .loopText,#mermaid-svg-IpaOkD1bFSzh9pUL .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IpaOkD1bFSzh9pUL .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IpaOkD1bFSzh9pUL .noteText,#mermaid-svg-IpaOkD1bFSzh9pUL .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IpaOkD1bFSzh9pUL .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IpaOkD1bFSzh9pUL .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IpaOkD1bFSzh9pUL .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IpaOkD1bFSzh9pUL .actorPopupMenu{position:absolute;}#mermaid-svg-IpaOkD1bFSzh9pUL .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-IpaOkD1bFSzh9pUL .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IpaOkD1bFSzh9pUL .actor-man circle,#mermaid-svg-IpaOkD1bFSzh9pUL line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IpaOkD1bFSzh9pUL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后续 Get 不再调用 eval(result 已缓存) Notify() (数据变更)cache.Swap(newCacheEntry)Get()cache.Load() → entryentry.get()lock.RLock → result==nil?lock.Lock → eval()(value, nil)(value, nil)Get()entry.get()lock.RLock → result≠nil(*result, nil)
4.10 RetryWithDeadline --- 带截止期限的重试 (retry_with_deadline.go, 78行)
go
type RetryWithDeadline interface {
After(error) // 记录错误
ShouldRetry() bool // 是否应该继续重试
}
type retryWithDeadlineImpl struct {
firstErrorTime time.Time // 首次错误时间
lastErrorTime time.Time // 最近一次错误时间
maxRetryDuration time.Duration // 最大重试时长
minResetPeriod time.Duration // 最小重置间隔
isRetryable func(error) bool
clock clock.Clock
}
核心逻辑:
go
func (r *retryWithDeadlineImpl) After(err error) {
if r.isRetryable(err) {
// 如果距离上次错误已超过 minResetPeriod → 重置计时
if r.clock.Now().Sub(r.lastErrorTime) >= r.minResetPeriod {
r.reset()
}
// 记录错误时间
if r.firstErrorTime.IsZero() {
r.firstErrorTime = r.clock.Now()
}
r.lastErrorTime = r.clock.Now()
}
}
func (r *retryWithDeadlineImpl) ShouldRetry() bool {
if r.maxRetryDuration <= 0 {
return false // 无限期
}
// 从首次错误到现在 ≤ maxRetryDuration → 可重试
if r.clock.Now().Sub(r.firstErrorTime) <= r.maxRetryDuration {
return true
}
r.reset()
return false // 超过截止期限 → 不重试
}
时间窗口示意:
#mermaid-svg-IOuPvB7ZiNMBvMbs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IOuPvB7ZiNMBvMbs .error-icon{fill:#552222;}#mermaid-svg-IOuPvB7ZiNMBvMbs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IOuPvB7ZiNMBvMbs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .marker.cross{stroke:#333333;}#mermaid-svg-IOuPvB7ZiNMBvMbs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IOuPvB7ZiNMBvMbs p{margin:0;}#mermaid-svg-IOuPvB7ZiNMBvMbs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .cluster-label text{fill:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .cluster-label span{color:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .cluster-label span p{background-color:transparent;}#mermaid-svg-IOuPvB7ZiNMBvMbs .label text,#mermaid-svg-IOuPvB7ZiNMBvMbs span{fill:#333;color:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .node rect,#mermaid-svg-IOuPvB7ZiNMBvMbs .node circle,#mermaid-svg-IOuPvB7ZiNMBvMbs .node ellipse,#mermaid-svg-IOuPvB7ZiNMBvMbs .node polygon,#mermaid-svg-IOuPvB7ZiNMBvMbs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .rough-node .label text,#mermaid-svg-IOuPvB7ZiNMBvMbs .node .label text,#mermaid-svg-IOuPvB7ZiNMBvMbs .image-shape .label,#mermaid-svg-IOuPvB7ZiNMBvMbs .icon-shape .label{text-anchor:middle;}#mermaid-svg-IOuPvB7ZiNMBvMbs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .rough-node .label,#mermaid-svg-IOuPvB7ZiNMBvMbs .node .label,#mermaid-svg-IOuPvB7ZiNMBvMbs .image-shape .label,#mermaid-svg-IOuPvB7ZiNMBvMbs .icon-shape .label{text-align:center;}#mermaid-svg-IOuPvB7ZiNMBvMbs .node.clickable{cursor:pointer;}#mermaid-svg-IOuPvB7ZiNMBvMbs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .arrowheadPath{fill:#333333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IOuPvB7ZiNMBvMbs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IOuPvB7ZiNMBvMbs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IOuPvB7ZiNMBvMbs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IOuPvB7ZiNMBvMbs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .cluster text{fill:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs .cluster span{color:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IOuPvB7ZiNMBvMbs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IOuPvB7ZiNMBvMbs rect.text{fill:none;stroke-width:0;}#mermaid-svg-IOuPvB7ZiNMBvMbs .icon-shape,#mermaid-svg-IOuPvB7ZiNMBvMbs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IOuPvB7ZiNMBvMbs .icon-shape p,#mermaid-svg-IOuPvB7ZiNMBvMbs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IOuPvB7ZiNMBvMbs .icon-shape .label rect,#mermaid-svg-IOuPvB7ZiNMBvMbs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IOuPvB7ZiNMBvMbs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IOuPvB7ZiNMBvMbs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IOuPvB7ZiNMBvMbs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 时间线
now - firstErrorTime ≤ maxRetryDuration
可重试 (ShouldRetry=true)
now - firstErrorTime > maxRetryDuration
firstErrorTime
now
firstErrorTime +
maxRetryDuration
继续重试
停止重试
4.11 ObjectName --- 对象名值类型 (object-names.go, 65行)
go
type ObjectName struct {
Namespace string
Name string
}
// String 编码与 MetaNamespaceKeyFunc 一致
func (objName ObjectName) String() string {
if len(objName.Namespace) > 0 {
return objName.Namespace + "/" + objName.Name
}
return objName.Name
}
// ParseObjectName 反向解析
func ParseObjectName(str string) (ObjectName, error) {
var objName ObjectName
objName.Namespace, objName.Name, err = SplitMetaNamespaceKey(str)
return objName, err
}
与 types.NamespacedName 的区别 :ObjectName.String() 输出 ns/name,而 NamespacedName.String() 输出 ns/name(格式相同但历史行为不同)。
4.12 event_handler_name --- EventHandler 命名 (event_handler_name.go, 121行)
为 ResourceEventHandler 生成可识别的名称,用于指标和日志。
go
func nameForHandler(handler ResourceEventHandler) string {
switch handler := handler.(type) {
case *ResourceEventHandlerFuncs:
return nameForHandlerFuncs(*handler)
case ResourceEventHandlerFuncs:
return nameForHandlerFuncs(handler)
default:
// 使用反射获取类型名
value := reflect.ValueOf(handler)
// 解引用指针/接口 → 获取包路径.类型名
return value.Type().PkgPath() + "." + value.Type().Name()
}
}
func nameForFunctions(fs ...any) string {
// 提取所有函数的限定名
// 如果所有限定名相同 → 返回公共限定名
// 否则 → 用 "+" 连接所有限定名
}
4.13 RealFIFO --- 新一代 FIFO 队列 (the_real_fifo.go, 859行)
4.13.1 核心设计差异
RealFIFO 是 DeltaFIFO 的下一代替代品 ,核心区别是不按 key 去重,所有 Delta 按序排列,支持批量处理和原子事件。
go
type RealFIFO struct {
logger klog.Logger
name string
lock sync.RWMutex
cond sync.Cond
items []Delta // 有序 Delta 列表(不去重!)
synced chan struct{} // 初始同步完成信号
syncedClosed bool
populated bool
initialPopulationCount int
keyFunc KeyFunc
knownObjects KeyListerGetter // AtomicEvents=true 时为 nil
closed bool
transformer TransformFunc
batchSize int // 默认 1000
emitAtomicEvents bool // 原子事件模式
unlockWhileProcessing bool // 处理时解锁
identifier InformerNameAndResource
metrics *fifoMetrics
emitDeltaTypeBookmark bool // Bookmark Delta
}
4.13.2 Add/Update/Delete --- 直接追加
go
func (f *RealFIFO) Add(obj interface{}) error {
f.lock.Lock()
defer f.lock.Unlock()
f.populated = true
f.checkSynced_locked()
return f.addToItems_locked(Added, false, obj)
// 直接追加到 items 列表,不去重
}
4.13.3 Pop --- 单条弹出
go
func (f *RealFIFO) Pop(process PopProcessFunc) (interface{}, error) {
f.lock.Lock()
defer f.lock.Unlock()
for len(f.items) == 0 {
if f.closed { return nil, ErrFIFOClosed }
f.cond.Wait()
}
isInInitialList := !f.hasSynced_locked()
item := f.items[0]
f.items[0] = Delta{} // 清除引用,帮助 GC
f.items = f.items[1:]
defer func() {
if f.initialPopulationCount > 0 {
f.initialPopulationCount--
f.checkSynced_locked()
}
}()
// 可选:处理时解锁
err := f.whileProcessing_locked(func() error {
return process(Deltas{item}, isInInitialList)
})
return Deltas{item}, err
}
4.13.4 PopBatch --- 批量弹出
go
func (f *RealFIFO) PopBatch(processBatch ProcessBatchFunc, processSingle PopProcessFunc) error {
// ... 阻塞等待非空 ...
unique := sets.NewString()
deltas := make([]Delta, 0, min(len(f.items), f.batchSize))
for i := 0; i < f.batchSize && i < len(f.items); i++ {
item := f.items[i]
if !batchable[item.Type] {
// 非批处理 Delta (ReplacedAll/SyncAll/Bookmark) → 单独处理
if len(deltas) == 0 { moveDeltaToProcessList(i) }
break
}
id, err := f.keyOf(item)
if err != nil || unique.Has(id) {
break // key 重复或出错 → 结束批处理
}
unique.Insert(id)
moveDeltaToProcessList(i)
}
if len(deltas) == 1 {
return processSingle(Deltas{deltas[0]}, isInInitialList)
}
return processBatch(deltas, isInInitialList)
}
4.13.5 Replace --- 原子 vs 非原子
go
func (f *RealFIFO) Replace(newItems []interface{}, rv string) error {
f.lock.Lock()
defer f.lock.Unlock()
if f.emitAtomicEvents {
// 原子模式:单个 ReplacedAll Delta
return f.addReplaceToItemsLocked(newItems, rv)
} else {
// 非原子模式:逐个 Added/Replaced + 合成 Deleted
return reconcileReplacement(...)
}
}
原子 Replace :所有新对象打包进一个 ReplacedAll Delta,消费者一次性处理。
go
type ReplacedAllInfo struct {
ResourceVersion string
Objects []interface{}
}
4.13.6 whileProcessing_locked --- 处理时可选解锁
go
func (f *RealFIFO) whileProcessing_locked(process func() error) error {
// 只有 AtomicEvents=true 且队列不太长时才解锁
if f.unlockWhileProcessing && len(f.items) < f.batchSize*2 {
f.lock.Unlock()
defer f.lock.Lock()
}
startTime := time.Now()
err := process()
f.metrics.processingLatency.Observe(time.Since(startTime).Seconds())
return err
}
设计权衡:解锁允许 Reflector 在处理期间继续添加 item,提高吞吐;但需要保证处理函数不依赖队列锁。
4.13.7 RealFIFO 构造校验
go
func NewRealFIFOWithOptions(opts RealFIFOOptions) *RealFIFO {
if opts.AtomicEvents {
// 原子模式 → knownObjects 必须为 nil
if opts.KnownObjects != nil {
panic("knownObjects must not be provided when AtomicEvents is true")
}
} else {
// 非原子模式 → 需要 knownObjects 做 Replace 去重
if opts.UnlockWhileProcessing {
panic("UnlockWhileProcessing must be false when AtomicEvents is false")
}
if opts.KnownObjects == nil {
panic("knownObjects must be provided when AtomicEvents is false")
}
if opts.EmitDeltaTypeBookmark {
panic("EmitDeltaTypeBookmark must be false when AtomicEvents is false")
}
}
// ...
}
RealFIFO 模式组合:
#mermaid-svg-mZutX23NjlKqWa7z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mZutX23NjlKqWa7z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mZutX23NjlKqWa7z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mZutX23NjlKqWa7z .error-icon{fill:#552222;}#mermaid-svg-mZutX23NjlKqWa7z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mZutX23NjlKqWa7z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mZutX23NjlKqWa7z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mZutX23NjlKqWa7z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mZutX23NjlKqWa7z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mZutX23NjlKqWa7z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mZutX23NjlKqWa7z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mZutX23NjlKqWa7z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mZutX23NjlKqWa7z .marker.cross{stroke:#333333;}#mermaid-svg-mZutX23NjlKqWa7z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mZutX23NjlKqWa7z p{margin:0;}#mermaid-svg-mZutX23NjlKqWa7z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mZutX23NjlKqWa7z .cluster-label text{fill:#333;}#mermaid-svg-mZutX23NjlKqWa7z .cluster-label span{color:#333;}#mermaid-svg-mZutX23NjlKqWa7z .cluster-label span p{background-color:transparent;}#mermaid-svg-mZutX23NjlKqWa7z .label text,#mermaid-svg-mZutX23NjlKqWa7z span{fill:#333;color:#333;}#mermaid-svg-mZutX23NjlKqWa7z .node rect,#mermaid-svg-mZutX23NjlKqWa7z .node circle,#mermaid-svg-mZutX23NjlKqWa7z .node ellipse,#mermaid-svg-mZutX23NjlKqWa7z .node polygon,#mermaid-svg-mZutX23NjlKqWa7z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mZutX23NjlKqWa7z .rough-node .label text,#mermaid-svg-mZutX23NjlKqWa7z .node .label text,#mermaid-svg-mZutX23NjlKqWa7z .image-shape .label,#mermaid-svg-mZutX23NjlKqWa7z .icon-shape .label{text-anchor:middle;}#mermaid-svg-mZutX23NjlKqWa7z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mZutX23NjlKqWa7z .rough-node .label,#mermaid-svg-mZutX23NjlKqWa7z .node .label,#mermaid-svg-mZutX23NjlKqWa7z .image-shape .label,#mermaid-svg-mZutX23NjlKqWa7z .icon-shape .label{text-align:center;}#mermaid-svg-mZutX23NjlKqWa7z .node.clickable{cursor:pointer;}#mermaid-svg-mZutX23NjlKqWa7z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mZutX23NjlKqWa7z .arrowheadPath{fill:#333333;}#mermaid-svg-mZutX23NjlKqWa7z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mZutX23NjlKqWa7z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mZutX23NjlKqWa7z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mZutX23NjlKqWa7z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mZutX23NjlKqWa7z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mZutX23NjlKqWa7z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mZutX23NjlKqWa7z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mZutX23NjlKqWa7z .cluster text{fill:#333;}#mermaid-svg-mZutX23NjlKqWa7z .cluster span{color:#333;}#mermaid-svg-mZutX23NjlKqWa7z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mZutX23NjlKqWa7z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mZutX23NjlKqWa7z rect.text{fill:none;stroke-width:0;}#mermaid-svg-mZutX23NjlKqWa7z .icon-shape,#mermaid-svg-mZutX23NjlKqWa7z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mZutX23NjlKqWa7z .icon-shape p,#mermaid-svg-mZutX23NjlKqWa7z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mZutX23NjlKqWa7z .icon-shape .label rect,#mermaid-svg-mZutX23NjlKqWa7z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mZutX23NjlKqWa7z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mZutX23NjlKqWa7z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mZutX23NjlKqWa7z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} true
false
AtomicEvents?
需要 KnownObjects
(Replace 需要合成 Deleted)
不需要 KnownObjects
(ReplacedAll 自包含)
可 UnlockWhileProcessing
(处理时不影响一致性)
不可 UnlockWhileProcessing
可 EmitDeltaTypeBookmark
(RV 有序保证)
不可 Bookmark