【client-go v0.36.1】(store Part 2)Store 超深度分析 — threadSafeMap 核心、索引体系、RV追踪、事务机制

Store 超深度分析 --- threadSafeMap 核心、索引体系、RV追踪、事务机制

基于 client-go v0.36.1 tools/cache/thread_safe_store.go (552行) + index.go (100行)


十二、threadSafeMap --- 线程安全存储核心

12.1 完整字段解析

go 复制代码
type threadSafeMap struct {
    lock  sync.RWMutex                // 读写锁:读操作用 RLock,写操作用 Lock
    items map[string]interface{}      // 核心存储:key → 对象(只存最新状态)
    index *storeIndex                 // 索引管理器(可能为空索引)
    rv    string                      // 最新观察到的 ResourceVersion
    metrics *storeMetrics             // 指标(RV Gauge)
}

12.2 Add → Update 等价性

go 复制代码
func (c *threadSafeMap) Add(key string, obj interface{}) {
    c.Update(key, obj)  // Add 直接调用 Update!
    // 设计意图:Store 中同一 key 的对象只保留最新值
    // 不区分"新增"和"更新"------都是覆盖写入
}

func (c *threadSafeMap) addLocked(key string, obj interface{}) {
    c.updateLocked(key, obj)  // 锁内版本也直接调用 updateLocked
}

与 DeltaFIFO 的根本区别:DeltaFIFO 累积 Delta(Added/Updated 是不同类型),Store 只保留最新值(Add = Update)。

12.3 Update 逐行解析

go 复制代码
func (c *threadSafeMap) Update(key string, obj interface{}) {
    // ─── Step 1: 从对象提取 RV ───
    rv, rvErr := rvFromObject(obj)           // meta.Accessor → GetResourceVersion
    rvInt, parseErr := parseRVForMetricsWithTruncation(rv)  // 截断为 15 位数字

    // ─── Step 2: 获取写锁 ───
    c.lock.Lock()
    defer c.lock.Unlock()

    // ─── Step 3: 更新存储 + 索引 ───
    c.updateLocked(key, obj)

    // ─── Step 4: 更新 RV + 指标 ───
    if rvErr == nil {
        c.rv = rv                           // 更新存储级 RV
        if parseErr == nil {
            c.metrics.storeResourceVersion.Set(float64(rvInt))  // 更新指标
        }
    }
}

12.4 updateLocked --- 核心写入逻辑

go 复制代码
func (c *threadSafeMap) updateLocked(key string, obj interface{}) {
    // ─── Step 1: 获取旧对象(可能为 nil) ───
    oldObject := c.items[key]

    // ─── Step 2: 写入新对象 ───
    c.items[key] = obj  // 直接覆盖

    // ─── Step 3: 更新索引 ───
    // updateIndices(oldObj, newObj, key):
    //   - oldObj != nil → 从旧索引值中删除 key
    //   - newObj != nil → 将 key 添加到新索引值中
    //   - 优化:单值且未变化 → 跳过(快速路径)
    c.index.updateIndices(oldObject, obj, key)
}

#mermaid-svg-MNRBadpaZhUAbcHZ{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-MNRBadpaZhUAbcHZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MNRBadpaZhUAbcHZ .error-icon{fill:#552222;}#mermaid-svg-MNRBadpaZhUAbcHZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MNRBadpaZhUAbcHZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MNRBadpaZhUAbcHZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MNRBadpaZhUAbcHZ .marker.cross{stroke:#333333;}#mermaid-svg-MNRBadpaZhUAbcHZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MNRBadpaZhUAbcHZ p{margin:0;}#mermaid-svg-MNRBadpaZhUAbcHZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ .cluster-label text{fill:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ .cluster-label span{color:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ .cluster-label span p{background-color:transparent;}#mermaid-svg-MNRBadpaZhUAbcHZ .label text,#mermaid-svg-MNRBadpaZhUAbcHZ span{fill:#333;color:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ .node rect,#mermaid-svg-MNRBadpaZhUAbcHZ .node circle,#mermaid-svg-MNRBadpaZhUAbcHZ .node ellipse,#mermaid-svg-MNRBadpaZhUAbcHZ .node polygon,#mermaid-svg-MNRBadpaZhUAbcHZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MNRBadpaZhUAbcHZ .rough-node .label text,#mermaid-svg-MNRBadpaZhUAbcHZ .node .label text,#mermaid-svg-MNRBadpaZhUAbcHZ .image-shape .label,#mermaid-svg-MNRBadpaZhUAbcHZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-MNRBadpaZhUAbcHZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MNRBadpaZhUAbcHZ .rough-node .label,#mermaid-svg-MNRBadpaZhUAbcHZ .node .label,#mermaid-svg-MNRBadpaZhUAbcHZ .image-shape .label,#mermaid-svg-MNRBadpaZhUAbcHZ .icon-shape .label{text-align:center;}#mermaid-svg-MNRBadpaZhUAbcHZ .node.clickable{cursor:pointer;}#mermaid-svg-MNRBadpaZhUAbcHZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MNRBadpaZhUAbcHZ .arrowheadPath{fill:#333333;}#mermaid-svg-MNRBadpaZhUAbcHZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MNRBadpaZhUAbcHZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MNRBadpaZhUAbcHZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MNRBadpaZhUAbcHZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MNRBadpaZhUAbcHZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MNRBadpaZhUAbcHZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MNRBadpaZhUAbcHZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MNRBadpaZhUAbcHZ .cluster text{fill:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ .cluster span{color:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ 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-MNRBadpaZhUAbcHZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MNRBadpaZhUAbcHZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-MNRBadpaZhUAbcHZ .icon-shape,#mermaid-svg-MNRBadpaZhUAbcHZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MNRBadpaZhUAbcHZ .icon-shape p,#mermaid-svg-MNRBadpaZhUAbcHZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MNRBadpaZhUAbcHZ .icon-shape .label rect,#mermaid-svg-MNRBadpaZhUAbcHZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MNRBadpaZhUAbcHZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MNRBadpaZhUAbcHZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MNRBadpaZhUAbcHZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} updateLocked(key, obj)
oldObject := c.itemskey
c.itemskey = obj
c.index.updateIndices(oldObject, obj, key)
遍历每个 indexName
updateSingleIndex(name, oldObj, newObj, key)
oldIndexValues = indexFunc(oldObj)
indexValues = indexFunc(newObj)
单值且未变?
跳过(快速路径)
deleteKeyFromIndex(key, oldVal, idx)
addKeyToIndex(key, newVal, idx)

12.5 DeleteWithObject 逐行解析

go 复制代码
func (c *threadSafeMap) DeleteWithObject(key string, obj interface{}) {
    var rv string
    var rvInt int64
    var rvErr, parseErr error

    // ─── Step 1: 从 obj 提取 RV(如果 obj != nil) ───
    if obj != nil {
        rv, rvErr = rvFromObject(obj)
        rvInt, parseErr = parseRVForMetricsWithTruncation(rv)
    }

    // ─── Step 2: 获取写锁 ───
    c.lock.Lock()
    defer c.lock.Unlock()

    // ─── Step 3: 删除 ───
    c.deleteLocked(key)

    // ─── Step 4: 更新 RV ───
    // 关键:删除也会更新 RV!
    // 原因:删除事件的 RV 可能比当前存储的 RV 更新
    // 如果不更新,store 的 RV 可能落后,导致 Watch 从旧 RV 开始
    if obj != nil && rvErr == nil {
        c.rv = rv
        if parseErr == nil {
            c.metrics.storeResourceVersion.Set(float64(rvInt))
        }
    }
}

12.6 Delete(key) --- 无 RV 更新

go 复制代码
func (c *threadSafeMap) Delete(key string) {
    c.DeleteWithObject(key, nil)  // obj=nil → 不更新 RV
}

为什么 cache.Delete 使用 DeleteWithObject?

go 复制代码
// cache.Delete:
c.cacheStorage.DeleteWithObject(key, obj)  // 传入 obj → 更新 RV

// 而不是:
c.cacheStorage.Delete(key)  // 不传 obj → 不更新 RV(RV 可能过时!)

结论:cache 层正确地使用了 DeleteWithObject,确保删除时也更新 RV。

12.7 deleteLocked --- 核心删除逻辑

go 复制代码
func (c *threadSafeMap) deleteLocked(key string) {
    if obj, exists := c.items[key]; exists {
        // ─── Step 1: 更新索引(从所有索引中移除 key) ───
        c.index.updateIndices(obj, nil, key)  // newObj=nil → 只删除不添加

        // ─── Step 2: 从 items 中删除 ───
        delete(c.items, key)
    }
    // 不存在 → 静默忽略(与 DeltaFIFO 不同,Store 的 Delete 不检查存在性)
}

十三、storeIndex --- 索引管理核心

13.1 数据结构

go 复制代码
type storeIndex struct {
    indexers Indexers    // map[indexName]IndexFunc  --- 索引函数注册表
    indices  Indices     // map[indexName]index      --- 索引数据
}

// 三级映射关系:
type index map[string]sets.Set[string]       // indexedValue → Set[storageKey]
type Indexers map[string]IndexFunc           // indexName → IndexFunc
type Indices map[string]index                // indexName → index

#mermaid-svg-KfcG3MmMy9wpT5L1{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-KfcG3MmMy9wpT5L1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KfcG3MmMy9wpT5L1 .error-icon{fill:#552222;}#mermaid-svg-KfcG3MmMy9wpT5L1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KfcG3MmMy9wpT5L1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .marker.cross{stroke:#333333;}#mermaid-svg-KfcG3MmMy9wpT5L1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KfcG3MmMy9wpT5L1 p{margin:0;}#mermaid-svg-KfcG3MmMy9wpT5L1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .cluster-label text{fill:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .cluster-label span{color:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .cluster-label span p{background-color:transparent;}#mermaid-svg-KfcG3MmMy9wpT5L1 .label text,#mermaid-svg-KfcG3MmMy9wpT5L1 span{fill:#333;color:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .node rect,#mermaid-svg-KfcG3MmMy9wpT5L1 .node circle,#mermaid-svg-KfcG3MmMy9wpT5L1 .node ellipse,#mermaid-svg-KfcG3MmMy9wpT5L1 .node polygon,#mermaid-svg-KfcG3MmMy9wpT5L1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .rough-node .label text,#mermaid-svg-KfcG3MmMy9wpT5L1 .node .label text,#mermaid-svg-KfcG3MmMy9wpT5L1 .image-shape .label,#mermaid-svg-KfcG3MmMy9wpT5L1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-KfcG3MmMy9wpT5L1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .rough-node .label,#mermaid-svg-KfcG3MmMy9wpT5L1 .node .label,#mermaid-svg-KfcG3MmMy9wpT5L1 .image-shape .label,#mermaid-svg-KfcG3MmMy9wpT5L1 .icon-shape .label{text-align:center;}#mermaid-svg-KfcG3MmMy9wpT5L1 .node.clickable{cursor:pointer;}#mermaid-svg-KfcG3MmMy9wpT5L1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .arrowheadPath{fill:#333333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KfcG3MmMy9wpT5L1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KfcG3MmMy9wpT5L1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KfcG3MmMy9wpT5L1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KfcG3MmMy9wpT5L1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .cluster text{fill:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 .cluster span{color:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 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-KfcG3MmMy9wpT5L1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KfcG3MmMy9wpT5L1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-KfcG3MmMy9wpT5L1 .icon-shape,#mermaid-svg-KfcG3MmMy9wpT5L1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KfcG3MmMy9wpT5L1 .icon-shape p,#mermaid-svg-KfcG3MmMy9wpT5L1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KfcG3MmMy9wpT5L1 .icon-shape .label rect,#mermaid-svg-KfcG3MmMy9wpT5L1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KfcG3MmMy9wpT5L1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KfcG3MmMy9wpT5L1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KfcG3MmMy9wpT5L1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 示例:namespace 索引
storeIndex
indexers

(IndexFunc 注册表)
indices

(索引数据)
indexers'namespace'

= MetaNamespaceIndexFunc
indices'namespace'

= index{...}
'default' → {'pod-a', 'pod-b'}
'kube-system' → {'coredns'}

完整索引查找路径

复制代码
Index("namespace", pod-obj) →
  1. indexers["namespace"](pod-obj) → ["default"]
  2. indices["namespace"]["default"] → {"pod-a", "pod-b"}
  3. items["pod-a"], items["pod-b"] → [podA, podB]

13.2 updateIndices --- 索引更新总入口

go 复制代码
func (i *storeIndex) updateIndices(oldObj interface{}, newObj interface{}, key string) {
    // 遍历所有已注册的索引
    for name := range i.indexers {
        i.updateSingleIndex(name, oldObj, newObj, key)
    }
}

13.3 updateSingleIndex 逐行解析

go 复制代码
func (i *storeIndex) updateSingleIndex(name string, oldObj interface{}, newObj interface{}, key string) {
    var oldIndexValues, indexValues []string

    // ─── Step 1: 获取 IndexFunc ───
    indexFunc, ok := i.indexers[name]
    if !ok {
        // 不存在 → panic(调用者应确保存在且持锁)
        panic(fmt.Errorf("indexer %q does not exist", name))
    }

    // ─── Step 2: 计算旧索引值 ───
    if oldObj != nil {
        var err error
        oldIndexValues, err = indexFunc(oldObj)
        if err != nil {
            // IndexFunc 失败 → panic(索引不一致比程序继续更危险)
            panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
        }
    } else {
        oldIndexValues = oldIndexValues[:0]  // nil → 空切片(创建时 oldObj 为 nil)
    }

    // ─── Step 3: 计算新索引值 ───
    if newObj != nil {
        var err error
        indexValues, err = indexFunc(newObj)
        if err != nil {
            panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
        }
    } else {
        indexValues = indexValues[:0]  // nil → 空切片(删除时 newObj 为 nil)
    }

    // ─── Step 4: 获取/创建 index ───
    idx := i.indices[name]
    if idx == nil {
        idx = index{}          // 首次使用此索引 → 创建空 index
        i.indices[name] = idx
    }

    // ─── Step 5: 快速路径优化 ───
    // 最常见情况:IndexFunc 返回单个值且未变化
    // 例如:namespace 索引,对象的 namespace 没变
    if len(indexValues) == 1 && len(oldIndexValues) == 1 && indexValues[0] == oldIndexValues[0] {
        return  // 跳过!无需更新索引
    }

    // ─── Step 6: 慢路径:先删后加 ───
    for _, value := range oldIndexValues {
        i.deleteKeyFromIndex(key, value, idx)  // 从旧索引值中删除 key
    }
    for _, value := range indexValues {
        i.addKeyToIndex(key, value, idx)       // 将 key 添加到新索引值中
    }
}

快速路径 vs 慢路径

#mermaid-svg-LO2DXqg72IeeTM29{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-LO2DXqg72IeeTM29 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LO2DXqg72IeeTM29 .error-icon{fill:#552222;}#mermaid-svg-LO2DXqg72IeeTM29 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LO2DXqg72IeeTM29 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LO2DXqg72IeeTM29 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LO2DXqg72IeeTM29 .marker.cross{stroke:#333333;}#mermaid-svg-LO2DXqg72IeeTM29 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LO2DXqg72IeeTM29 p{margin:0;}#mermaid-svg-LO2DXqg72IeeTM29 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LO2DXqg72IeeTM29 .cluster-label text{fill:#333;}#mermaid-svg-LO2DXqg72IeeTM29 .cluster-label span{color:#333;}#mermaid-svg-LO2DXqg72IeeTM29 .cluster-label span p{background-color:transparent;}#mermaid-svg-LO2DXqg72IeeTM29 .label text,#mermaid-svg-LO2DXqg72IeeTM29 span{fill:#333;color:#333;}#mermaid-svg-LO2DXqg72IeeTM29 .node rect,#mermaid-svg-LO2DXqg72IeeTM29 .node circle,#mermaid-svg-LO2DXqg72IeeTM29 .node ellipse,#mermaid-svg-LO2DXqg72IeeTM29 .node polygon,#mermaid-svg-LO2DXqg72IeeTM29 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LO2DXqg72IeeTM29 .rough-node .label text,#mermaid-svg-LO2DXqg72IeeTM29 .node .label text,#mermaid-svg-LO2DXqg72IeeTM29 .image-shape .label,#mermaid-svg-LO2DXqg72IeeTM29 .icon-shape .label{text-anchor:middle;}#mermaid-svg-LO2DXqg72IeeTM29 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LO2DXqg72IeeTM29 .rough-node .label,#mermaid-svg-LO2DXqg72IeeTM29 .node .label,#mermaid-svg-LO2DXqg72IeeTM29 .image-shape .label,#mermaid-svg-LO2DXqg72IeeTM29 .icon-shape .label{text-align:center;}#mermaid-svg-LO2DXqg72IeeTM29 .node.clickable{cursor:pointer;}#mermaid-svg-LO2DXqg72IeeTM29 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LO2DXqg72IeeTM29 .arrowheadPath{fill:#333333;}#mermaid-svg-LO2DXqg72IeeTM29 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LO2DXqg72IeeTM29 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LO2DXqg72IeeTM29 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LO2DXqg72IeeTM29 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LO2DXqg72IeeTM29 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LO2DXqg72IeeTM29 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LO2DXqg72IeeTM29 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LO2DXqg72IeeTM29 .cluster text{fill:#333;}#mermaid-svg-LO2DXqg72IeeTM29 .cluster span{color:#333;}#mermaid-svg-LO2DXqg72IeeTM29 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-LO2DXqg72IeeTM29 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LO2DXqg72IeeTM29 rect.text{fill:none;stroke-width:0;}#mermaid-svg-LO2DXqg72IeeTM29 .icon-shape,#mermaid-svg-LO2DXqg72IeeTM29 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LO2DXqg72IeeTM29 .icon-shape p,#mermaid-svg-LO2DXqg72IeeTM29 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LO2DXqg72IeeTM29 .icon-shape .label rect,#mermaid-svg-LO2DXqg72IeeTM29 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LO2DXqg72IeeTM29 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LO2DXqg72IeeTM29 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LO2DXqg72IeeTM29 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes (最常见)
No
updateSingleIndex(name, oldObj, newObj, key)
oldIndexValues = indexFunc(oldObj)
indexValues = indexFunc(newObj)
len==1 &&

old0==new0?
return (快速路径)

索引值未变,无需更新
遍历 oldIndexValues

deleteKeyFromIndex()
遍历 indexValues

addKeyToIndex()

设计洞察:namespace 索引是最常见的索引(几乎所有 Informer 都使用)。Update 时 namespace 几乎不变,快速路径跳过索引更新能显著提升性能。

panic 而非 error 的设计选择

IndexFunc 失败时 panic 而非返回 error。原因:

  • 索引不一致比程序崩溃更危险(会导致查询结果错误)
  • 调用者已经持锁,返回 error 后的处理路径复杂
  • IndexFunc 应该是确定性的,失败意味着严重 bug

13.4 addKeyToIndex

go 复制代码
func (i *storeIndex) addKeyToIndex(key, indexValue string, index index) {
    set := index[indexValue]
    if set == nil {
        set = sets.Set[string]{}      // 首次出现此 indexedValue → 创建 Set
        index[indexValue] = set
    }
    set.Insert(key)                   // 将 storageKey 添加到 Set
    // Set 自动去重:同一 key 多次 Insert 只存一次
}

13.5 deleteKeyFromIndex

go 复制代码
func (i *storeIndex) deleteKeyFromIndex(key, indexValue string, index index) {
    set := index[indexValue]
    if set == nil {
        return  // indexedValue 不存在 → 跳过
    }
    set.Delete(key)  // 从 Set 中删除 key

    // ─── 关键优化:清理空 Set ───
    if len(set) == 0 {
        delete(index, indexValue)
        // 设计原因:高基数、短生命周期的资源(如 Pod)
        // 如果不清理空 Set,index map 会持续增长(内存泄漏)
        // 参考:kubernetes/kubernetes/issues/84959
    }
}

#mermaid-svg-CjXtkuC6MvBTgD6F{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-CjXtkuC6MvBTgD6F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CjXtkuC6MvBTgD6F .error-icon{fill:#552222;}#mermaid-svg-CjXtkuC6MvBTgD6F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CjXtkuC6MvBTgD6F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CjXtkuC6MvBTgD6F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CjXtkuC6MvBTgD6F .marker.cross{stroke:#333333;}#mermaid-svg-CjXtkuC6MvBTgD6F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CjXtkuC6MvBTgD6F p{margin:0;}#mermaid-svg-CjXtkuC6MvBTgD6F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F .cluster-label text{fill:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F .cluster-label span{color:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F .cluster-label span p{background-color:transparent;}#mermaid-svg-CjXtkuC6MvBTgD6F .label text,#mermaid-svg-CjXtkuC6MvBTgD6F span{fill:#333;color:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F .node rect,#mermaid-svg-CjXtkuC6MvBTgD6F .node circle,#mermaid-svg-CjXtkuC6MvBTgD6F .node ellipse,#mermaid-svg-CjXtkuC6MvBTgD6F .node polygon,#mermaid-svg-CjXtkuC6MvBTgD6F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CjXtkuC6MvBTgD6F .rough-node .label text,#mermaid-svg-CjXtkuC6MvBTgD6F .node .label text,#mermaid-svg-CjXtkuC6MvBTgD6F .image-shape .label,#mermaid-svg-CjXtkuC6MvBTgD6F .icon-shape .label{text-anchor:middle;}#mermaid-svg-CjXtkuC6MvBTgD6F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CjXtkuC6MvBTgD6F .rough-node .label,#mermaid-svg-CjXtkuC6MvBTgD6F .node .label,#mermaid-svg-CjXtkuC6MvBTgD6F .image-shape .label,#mermaid-svg-CjXtkuC6MvBTgD6F .icon-shape .label{text-align:center;}#mermaid-svg-CjXtkuC6MvBTgD6F .node.clickable{cursor:pointer;}#mermaid-svg-CjXtkuC6MvBTgD6F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CjXtkuC6MvBTgD6F .arrowheadPath{fill:#333333;}#mermaid-svg-CjXtkuC6MvBTgD6F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CjXtkuC6MvBTgD6F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CjXtkuC6MvBTgD6F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CjXtkuC6MvBTgD6F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CjXtkuC6MvBTgD6F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CjXtkuC6MvBTgD6F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CjXtkuC6MvBTgD6F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CjXtkuC6MvBTgD6F .cluster text{fill:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F .cluster span{color:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F 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-CjXtkuC6MvBTgD6F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CjXtkuC6MvBTgD6F rect.text{fill:none;stroke-width:0;}#mermaid-svg-CjXtkuC6MvBTgD6F .icon-shape,#mermaid-svg-CjXtkuC6MvBTgD6F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CjXtkuC6MvBTgD6F .icon-shape p,#mermaid-svg-CjXtkuC6MvBTgD6F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CjXtkuC6MvBTgD6F .icon-shape .label rect,#mermaid-svg-CjXtkuC6MvBTgD6F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CjXtkuC6MvBTgD6F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CjXtkuC6MvBTgD6F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CjXtkuC6MvBTgD6F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 删除 pod-b (清理)
删除 pod-a
删除前
index'default'

= {'pod-a', 'pod-b'}
index'default'

= {'pod-b'}
index 中无 'default' 键

(空 Set 被删除)


十四、索引查询方法深度解析

14.1 Index --- 按对象查找

go 复制代码
func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) {
    c.lock.RLock()
    defer c.lock.RUnlock()

    // ─── Step 1: 通过 obj 的索引值查找 key 集合 ───
    storeKeySet, err := c.index.getKeysFromIndex(indexName, obj)
    if err != nil {
        return nil, err
    }

    // ─── Step 2: 从 items 中获取对象 ───
    list := make([]interface{}, 0, storeKeySet.Len())
    for storeKey := range storeKeySet {
        list = append(list, c.items[storeKey])
    }
    return list, nil
}

14.2 getKeysFromIndex 逐行解析

go 复制代码
func (i *storeIndex) getKeysFromIndex(indexName string, obj interface{}) (sets.Set[string], error) {
    // ─── Step 1: 获取 IndexFunc ───
    indexFunc := i.indexers[indexName]
    if indexFunc == nil {
        return nil, fmt.Errorf("Index with name %s does not exist", indexName)
    }

    // ─── Step 2: 对 obj 计算索引值 ───
    indexedValues, err := indexFunc(obj)
    if err != nil {
        return nil, err
    }

    // ─── Step 3: 查找 index ───
    index := i.indices[indexName]

    // ─── Step 4: 合并 key 集合 ───
    var storeKeySet sets.Set[string]
    if len(indexedValues) == 1 {
        // ─── 快速路径:单值 ───
        // 最常见情况(如 namespace 索引只返回一个值)
        // 直接取 Set,无需去重
        storeKeySet = index[indexedValues[0]]
    } else {
        // ─── 慢速路径:多值 ───
        // 需要合并多个 Set 并去重
        storeKeySet = sets.Set[string]{}
        for _, indexedValue := range indexedValues {
            for key := range index[indexedValue] {
                storeKeySet.Insert(key)  // Set 自动去重
            }
        }
    }
    return storeKeySet, nil
}

为什么单值不需要去重? 因为单个 indexedValue 对应的 Set 本身就无重复。但多个 indexedValue 可能有交集(同一个 key 出现在多个 indexedValue 的 Set 中),所以需要去重。

14.3 ByIndex --- 按索引名+值精确查找

go 复制代码
func (c *threadSafeMap) ByIndex(indexName, indexedValue string) ([]interface{}, error) {
    c.lock.RLock()
    defer c.lock.RUnlock()

    set, err := c.index.getKeysByIndex(indexName, indexedValue)
    if err != nil {
        return nil, err
    }

    list := make([]interface{}, 0, set.Len())
    for key := range set {
        list = append(list, c.items[key])
    }
    return list, nil
}

14.4 getKeysByIndex

go 复制代码
func (i *storeIndex) getKeysByIndex(indexName, indexedValue string) (sets.Set[string], error) {
    indexFunc := i.indexers[indexName]
    if indexFunc == nil {
        return nil, fmt.Errorf("Index with name %s does not exist", indexName)
    }
    // 注意:indexFunc 存在性检查但未使用 indexFunc
    // 原因:只验证 indexName 是否合法,实际查找直接用 indices

    index := i.indices[indexName]
    return index[indexedValue], nil  // 直接返回 Set(可能为 nil)
}

14.5 IndexKeys --- 只返回 key

go 复制代码
func (c *threadSafeMap) IndexKeys(indexName, indexedValue string) ([]string, error) {
    c.lock.RLock()
    defer c.lock.RUnlock()

    set, err := c.index.getKeysByIndex(indexName, indexedValue)
    if err != nil { return nil, err }
    return sets.List(set), nil  // Set 转为排序后的 []string
}

14.6 ListIndexFuncValues --- 列出索引值

go 复制代码
func (i *storeIndex) getIndexValues(indexName string) []string {
    index := i.indices[indexName]
    names := make([]string, 0, len(index))
    for key := range index {
        names = append(names, key)  // 所有 indexedValue
    }
    return names
}

14.7 查询方法对比

方法 输入 输出 去重 用途
Index(name, obj) 对象 对象列表 ✅ (多值时) "查找与 obj 相似索引值的对象"
ByIndex(name, value) 索引名+值 对象列表 --- "查找指定索引值的所有对象"
IndexKeys(name, value) 索引名+值 key 列表 --- "查找指定索引值的所有 key"
ListIndexFuncValues(name) 索引名 值列表 --- "列出索引的所有值"

#mermaid-svg-w3ylFaQqeJqnTlsU{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-w3ylFaQqeJqnTlsU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w3ylFaQqeJqnTlsU .error-icon{fill:#552222;}#mermaid-svg-w3ylFaQqeJqnTlsU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w3ylFaQqeJqnTlsU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w3ylFaQqeJqnTlsU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w3ylFaQqeJqnTlsU .marker.cross{stroke:#333333;}#mermaid-svg-w3ylFaQqeJqnTlsU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w3ylFaQqeJqnTlsU p{margin:0;}#mermaid-svg-w3ylFaQqeJqnTlsU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU .cluster-label text{fill:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU .cluster-label span{color:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU .cluster-label span p{background-color:transparent;}#mermaid-svg-w3ylFaQqeJqnTlsU .label text,#mermaid-svg-w3ylFaQqeJqnTlsU span{fill:#333;color:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU .node rect,#mermaid-svg-w3ylFaQqeJqnTlsU .node circle,#mermaid-svg-w3ylFaQqeJqnTlsU .node ellipse,#mermaid-svg-w3ylFaQqeJqnTlsU .node polygon,#mermaid-svg-w3ylFaQqeJqnTlsU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w3ylFaQqeJqnTlsU .rough-node .label text,#mermaid-svg-w3ylFaQqeJqnTlsU .node .label text,#mermaid-svg-w3ylFaQqeJqnTlsU .image-shape .label,#mermaid-svg-w3ylFaQqeJqnTlsU .icon-shape .label{text-anchor:middle;}#mermaid-svg-w3ylFaQqeJqnTlsU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w3ylFaQqeJqnTlsU .rough-node .label,#mermaid-svg-w3ylFaQqeJqnTlsU .node .label,#mermaid-svg-w3ylFaQqeJqnTlsU .image-shape .label,#mermaid-svg-w3ylFaQqeJqnTlsU .icon-shape .label{text-align:center;}#mermaid-svg-w3ylFaQqeJqnTlsU .node.clickable{cursor:pointer;}#mermaid-svg-w3ylFaQqeJqnTlsU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w3ylFaQqeJqnTlsU .arrowheadPath{fill:#333333;}#mermaid-svg-w3ylFaQqeJqnTlsU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w3ylFaQqeJqnTlsU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w3ylFaQqeJqnTlsU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w3ylFaQqeJqnTlsU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w3ylFaQqeJqnTlsU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w3ylFaQqeJqnTlsU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w3ylFaQqeJqnTlsU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w3ylFaQqeJqnTlsU .cluster text{fill:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU .cluster span{color:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU 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-w3ylFaQqeJqnTlsU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w3ylFaQqeJqnTlsU rect.text{fill:none;stroke-width:0;}#mermaid-svg-w3ylFaQqeJqnTlsU .icon-shape,#mermaid-svg-w3ylFaQqeJqnTlsU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w3ylFaQqeJqnTlsU .icon-shape p,#mermaid-svg-w3ylFaQqeJqnTlsU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w3ylFaQqeJqnTlsU .icon-shape .label rect,#mermaid-svg-w3ylFaQqeJqnTlsU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w3ylFaQqeJqnTlsU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w3ylFaQqeJqnTlsU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w3ylFaQqeJqnTlsU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ListIndexFuncValues('namespace')
indices'namespace' → index
遍历 index → \[\]string
IndexKeys('namespace', 'default')
indices'namespace''default' → Set
sets.List(Set) → \[\]string
ByIndex('namespace', 'default')
indices'namespace''default' → Set
itemskey → 对象列表
Index(name, pod-obj)
indexFunc(pod-obj) → 'default'
indices'namespace''default' → Set
itemskey → 对象列表


十五、Replace --- 原子替换与索引重建

15.1 逐行解析

go 复制代码
func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) {
    var rvInt int64
    var parseErr error
    if resourceVersion != "" {
        rvInt, parseErr = parseRVForMetricsWithTruncation(resourceVersion)
    }

    c.lock.Lock()
    defer c.lock.Unlock()

    // ─── Step 1: 原子替换 items ───
    c.items = items           // 直接替换整个 map
    c.rv = resourceVersion    // 更新 RV

    // ─── Step 2: 更新指标 ───
    if parseErr == nil {
        c.metrics.storeResourceVersion.Set(float64(rvInt))
    }

    // ─── Step 3: 重建索引 ───
    c.index.reset()          // 清空所有索引数据
    for key, item := range c.items {
        c.index.updateIndices(nil, item, key)  // 从零构建索引
        // oldObj=nil → 只添加不删除
    }
}

索引重建流程

#mermaid-svg-JlbkKso4QQgj4xXS{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-JlbkKso4QQgj4xXS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JlbkKso4QQgj4xXS .error-icon{fill:#552222;}#mermaid-svg-JlbkKso4QQgj4xXS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JlbkKso4QQgj4xXS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JlbkKso4QQgj4xXS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JlbkKso4QQgj4xXS .marker.cross{stroke:#333333;}#mermaid-svg-JlbkKso4QQgj4xXS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JlbkKso4QQgj4xXS p{margin:0;}#mermaid-svg-JlbkKso4QQgj4xXS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JlbkKso4QQgj4xXS .cluster-label text{fill:#333;}#mermaid-svg-JlbkKso4QQgj4xXS .cluster-label span{color:#333;}#mermaid-svg-JlbkKso4QQgj4xXS .cluster-label span p{background-color:transparent;}#mermaid-svg-JlbkKso4QQgj4xXS .label text,#mermaid-svg-JlbkKso4QQgj4xXS span{fill:#333;color:#333;}#mermaid-svg-JlbkKso4QQgj4xXS .node rect,#mermaid-svg-JlbkKso4QQgj4xXS .node circle,#mermaid-svg-JlbkKso4QQgj4xXS .node ellipse,#mermaid-svg-JlbkKso4QQgj4xXS .node polygon,#mermaid-svg-JlbkKso4QQgj4xXS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JlbkKso4QQgj4xXS .rough-node .label text,#mermaid-svg-JlbkKso4QQgj4xXS .node .label text,#mermaid-svg-JlbkKso4QQgj4xXS .image-shape .label,#mermaid-svg-JlbkKso4QQgj4xXS .icon-shape .label{text-anchor:middle;}#mermaid-svg-JlbkKso4QQgj4xXS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JlbkKso4QQgj4xXS .rough-node .label,#mermaid-svg-JlbkKso4QQgj4xXS .node .label,#mermaid-svg-JlbkKso4QQgj4xXS .image-shape .label,#mermaid-svg-JlbkKso4QQgj4xXS .icon-shape .label{text-align:center;}#mermaid-svg-JlbkKso4QQgj4xXS .node.clickable{cursor:pointer;}#mermaid-svg-JlbkKso4QQgj4xXS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JlbkKso4QQgj4xXS .arrowheadPath{fill:#333333;}#mermaid-svg-JlbkKso4QQgj4xXS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JlbkKso4QQgj4xXS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JlbkKso4QQgj4xXS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JlbkKso4QQgj4xXS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JlbkKso4QQgj4xXS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JlbkKso4QQgj4xXS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JlbkKso4QQgj4xXS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JlbkKso4QQgj4xXS .cluster text{fill:#333;}#mermaid-svg-JlbkKso4QQgj4xXS .cluster span{color:#333;}#mermaid-svg-JlbkKso4QQgj4xXS 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-JlbkKso4QQgj4xXS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JlbkKso4QQgj4xXS rect.text{fill:none;stroke-width:0;}#mermaid-svg-JlbkKso4QQgj4xXS .icon-shape,#mermaid-svg-JlbkKso4QQgj4xXS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JlbkKso4QQgj4xXS .icon-shape p,#mermaid-svg-JlbkKso4QQgj4xXS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JlbkKso4QQgj4xXS .icon-shape .label rect,#mermaid-svg-JlbkKso4QQgj4xXS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JlbkKso4QQgj4xXS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JlbkKso4QQgj4xXS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JlbkKso4QQgj4xXS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 遍历完
Replace(items, rv)
获取写锁
c.items = items
c.rv = rv
更新 storeResourceVersion 指标
c.index.reset()
遍历 c.items
c.index.updateIndices(nil, item, key)
释放锁

reset --- 清空索引数据

go 复制代码
func (i *storeIndex) reset() {
    i.indices = Indices{}  // 分配新的空 Indices
    // 不清空 indexers!
    // 原因:indexers 是索引函数定义,Replace 后索引规则不变
    // 只需要清空 indices(索引数据),然后从新 items 重建
}

设计意图:Replace 是 O(n) 操作(n = items 数量),因为需要为每个对象重新计算所有索引值。


十六、ResourceVersion 追踪体系

16.1 rvFromObject

go 复制代码
func rvFromObject(obj interface{}) (rv string, err error) {
    meta, err := meta.Accessor(obj)  // 提取 metav1.Object 接口
    if err != nil {
        return "", err  // 不是 metav1.Object → 无法提取 RV
    }
    rv = meta.GetResourceVersion()
    return rv, nil
}

16.2 RV 更新时机

操作 RV 来源 更新条件
Update(key, obj) obj 的 ResourceVersion rvErr == nil
Add(key, obj) 同 Update(Add 调用 Update) 同 Update
DeleteWithObject(key, obj) obj 的 ResourceVersion obj != nil && rvErr == nil
Delete(key) 不更新
Replace(items, rv) 参数 resourceVersion rv != "" && parseErr == nil
Bookmark(rv) 参数 rv rv != "" && parseErr == nil
Transaction(txns) 最后一个 txn 的 obj rvErr == nil

16.3 LastStoreSyncResourceVersion --- Feature Gate 控制

go 复制代码
func (c *threadSafeMap) LastStoreSyncResourceVersion() string {
    // ─── Feature Gate 检查 ───
    if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.AtomicFIFO) {
        return ""  // 未启用 → 返回空字符串
    }
    // 原因:AtomicFIFO 使用 store 的 RV 来确定 Watch 起始点
    // 如果 Feature Gate 未启用,暴露 RV 可能导致不一致

    c.lock.RLock()
    defer c.lock.RUnlock()
    return c.rv
}

16.4 Bookmark --- 不改变对象,只更新 RV

go 复制代码
func (c *threadSafeMap) Bookmark(rv string) {
    var rvInt int64
    var parseErr error
    if rv != "" {
        rvInt, parseErr = parseRVForMetricsWithTruncation(rv)
    }
    c.lock.Lock()
    defer c.lock.Unlock()
    c.rv = rv
    if parseErr == nil {
        c.metrics.storeResourceVersion.Set(float64(rvInt))
    }
}

设计意图:Watch 的 Bookmark 事件只携带 RV,不改变对象。Store 通过 Bookmark 更新 RV,确保 Watch 断连重续时从最新 RV 开始。

16.5 parseRVForMetricsWithTruncation --- RV 截断

go 复制代码
func parseRVForMetricsWithTruncation(rv string) (int64, error) {
    if rv == "" { return 0, nil }

    // 截断为最后 15 位数字
    // 原因:float64 精确表示的整数上限是 2^53-1
    // Kubernetes 的 RV 可能超过此值(特别是在 etcd v3 中)
    // 截断后仍能反映 RV 的相对大小(用于监控趋势)
    if len(rv) > 15 {
        rv = rv[len(rv)-15:]
    }
    return strconv.ParseInt(rv, 10, 64)
}

十七、Transaction --- 批量操作

17.1 threadSafeMap.Transaction 逐行解析

go 复制代码
func (c *threadSafeMap) Transaction(txns ...ThreadSafeStoreTransaction) {
    if len(txns) == 0 { return }  // 空事务 → 直接返回

    // ─── Step 1: 从最后一个事务提取 RV ───
    // 设计意图:事务的 RV 以最后一个操作为准
    // 原因:事务内的操作按顺序执行,最后的 RV 最准确
    finalObj := txns[len(txns)-1].Object
    rv, rvErr := rvFromObject(finalObj)
    rvInt, parseErr := parseRVForMetricsWithTruncation(rv)

    // ─── Step 2: 获取写锁(整个事务只获取一次锁) ───
    c.lock.Lock()
    defer c.lock.Unlock()

    // ─── Step 3: 性能追踪 ───
    trace := utiltrace.New("ThreadSafeMap Transaction Process",
        utiltrace.Field{Key: "Size", Value: len(txns)},
        utiltrace.Field{Key: "Reason", Value: "Slow batch process due to too many items"})
    // 超时阈值:min(500ms * len(txns), 5s)
    defer trace.LogIfLong(min(500*time.Millisecond*time.Duration(len(txns)), 5*time.Second))

    // ─── Step 4: 顺序执行每个事务 ───
    for _, txn := range txns {
        switch txn.Type {
        case TransactionTypeAdd:
            c.addLocked(txn.Key, txn.Object)     // addLocked → updateLocked
        case TransactionTypeUpdate:
            c.updateLocked(txn.Key, txn.Object)  // 直接 updateLocked
        case TransactionTypeDelete:
            c.deleteLocked(txn.Key)               // 删除(不传 obj → 不更新 RV)
        }
    }

    // ─── Step 5: 更新 RV ───
    if rvErr == nil {
        c.rv = rv
        if parseErr == nil {
            c.metrics.storeResourceVersion.Set(float64(rvInt))
        }
    }
}

事务 vs 逐个操作

维度 逐个操作 (Add/Update/Delete) Transaction
锁获取次数 N 次 1 次
RV 更新 每次操作 最后一次
指标更新 每次操作 最后一次
性能 O(N) 锁开销 O(1) 锁开销
适用场景 单个对象操作 handleDeltas 批量处理

渲染错误: Mermaid 渲染失败: Parse error on line 16: ... Lock Note: 3次锁获取/释放 end ----------------------^ Expecting 'over', 'left_of', 'right_of', got 'TXT'


十八、AddIndexers --- 动态添加索引

18.1 逐行解析

go 复制代码
func (c *threadSafeMap) AddIndexers(newIndexers Indexers) error {
    c.lock.Lock()
    defer c.lock.Unlock()

    // ─── Step 1: 检查冲突 ───
    if err := c.index.addIndexers(newIndexers); err != nil {
        return err  // 已有同名索引 → 报错
    }

    // ─── Step 2: 为已有对象构建新索引 ───
    for key, item := range c.items {
        for name := range newIndexers {
            c.index.updateSingleIndex(name, nil, item, key)
            // oldObj=nil → 只添加不删除
        }
    }
    return nil
}

18.2 addIndexers --- 冲突检测

go 复制代码
func (i *storeIndex) addIndexers(newIndexers Indexers) error {
    oldKeys := sets.KeySet(i.indexers)      // 已有的索引名
    newKeys := sets.KeySet(newIndexers)      // 新的索引名

    if oldKeys.HasAny(sets.List(newKeys)...) {
        // 有交集 → 冲突!
        return fmt.Errorf("indexer conflict: %v", oldKeys.Intersection(newKeys))
    }

    // 无冲突 → 合并
    for k, v := range newIndexers {
        i.indexers[k] = v
    }
    return nil
}

设计约束:索引名不能重复。如果已注册 "namespace" 索引,不能再次注册同名索引。


十九、内置索引函数

19.1 NamespaceIndex + MetaNamespaceIndexFunc

go 复制代码
const (
    NamespaceIndex string = "namespace"  // 内置索引名
)

func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) {
    meta, err := meta.Accessor(obj)
    if err != nil {
        return []string{""}, fmt.Errorf("object has no meta: %v", err)
        // 注意:错误时返回 [""] 而非空切片
        // 原因:非 meta 对象归入 "" (空 namespace) 类别
    }
    return []string{meta.GetNamespace()}, nil
}

默认索引配置

go 复制代码
// SharedInformer 默认创建的 Indexer
NewIndexer(DeletionHandlingMetaNamespaceKeyFunc,
    Indexers{
        NamespaceIndex: MetaNamespaceIndexFunc,
    })

#mermaid-svg-1xYBXEnLSE4evwc4{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-1xYBXEnLSE4evwc4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1xYBXEnLSE4evwc4 .error-icon{fill:#552222;}#mermaid-svg-1xYBXEnLSE4evwc4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1xYBXEnLSE4evwc4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1xYBXEnLSE4evwc4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1xYBXEnLSE4evwc4 .marker.cross{stroke:#333333;}#mermaid-svg-1xYBXEnLSE4evwc4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1xYBXEnLSE4evwc4 p{margin:0;}#mermaid-svg-1xYBXEnLSE4evwc4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 .cluster-label text{fill:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 .cluster-label span{color:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 .cluster-label span p{background-color:transparent;}#mermaid-svg-1xYBXEnLSE4evwc4 .label text,#mermaid-svg-1xYBXEnLSE4evwc4 span{fill:#333;color:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 .node rect,#mermaid-svg-1xYBXEnLSE4evwc4 .node circle,#mermaid-svg-1xYBXEnLSE4evwc4 .node ellipse,#mermaid-svg-1xYBXEnLSE4evwc4 .node polygon,#mermaid-svg-1xYBXEnLSE4evwc4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1xYBXEnLSE4evwc4 .rough-node .label text,#mermaid-svg-1xYBXEnLSE4evwc4 .node .label text,#mermaid-svg-1xYBXEnLSE4evwc4 .image-shape .label,#mermaid-svg-1xYBXEnLSE4evwc4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1xYBXEnLSE4evwc4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1xYBXEnLSE4evwc4 .rough-node .label,#mermaid-svg-1xYBXEnLSE4evwc4 .node .label,#mermaid-svg-1xYBXEnLSE4evwc4 .image-shape .label,#mermaid-svg-1xYBXEnLSE4evwc4 .icon-shape .label{text-align:center;}#mermaid-svg-1xYBXEnLSE4evwc4 .node.clickable{cursor:pointer;}#mermaid-svg-1xYBXEnLSE4evwc4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1xYBXEnLSE4evwc4 .arrowheadPath{fill:#333333;}#mermaid-svg-1xYBXEnLSE4evwc4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1xYBXEnLSE4evwc4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1xYBXEnLSE4evwc4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1xYBXEnLSE4evwc4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1xYBXEnLSE4evwc4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1xYBXEnLSE4evwc4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1xYBXEnLSE4evwc4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1xYBXEnLSE4evwc4 .cluster text{fill:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 .cluster span{color:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 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-1xYBXEnLSE4evwc4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1xYBXEnLSE4evwc4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1xYBXEnLSE4evwc4 .icon-shape,#mermaid-svg-1xYBXEnLSE4evwc4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1xYBXEnLSE4evwc4 .icon-shape p,#mermaid-svg-1xYBXEnLSE4evwc4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1xYBXEnLSE4evwc4 .icon-shape .label rect,#mermaid-svg-1xYBXEnLSE4evwc4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1xYBXEnLSE4evwc4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1xYBXEnLSE4evwc4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1xYBXEnLSE4evwc4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NamespaceIndex
示例数据
pod-a (ns=default)
pod-b (ns=default)
coredns (ns=kube-system)
'default' → {'default/pod-a', 'default/pod-b'}
'kube-system' → {'kube-system/coredns'}


二十、Get/List 的线程安全语义

20.1 Get --- RLock

go 复制代码
func (c *threadSafeMap) Get(key string) (item interface{}, exists bool) {
    c.lock.RLock()
    defer c.lock.RUnlock()
    item, exists = c.items[key]
    return item, exists
}

20.2 List --- RLock + 新建切片

go 复制代码
func (c *threadSafeMap) List() []interface{} {
    c.lock.RLock()
    defer c.lock.RUnlock()
    list := make([]interface{}, 0, len(c.items))
    for _, item := range c.items {
        list = append(list, item)  // 返回的是指针!不是深拷贝!
    }
    return list
}

关键警告(源码注释):

TL;DR caveats: you must not modify anything returned by Get or List as it will break the indexing feature in addition to not being thread safe.

List 返回的是对象的指针,不是深拷贝。如果调用者修改了返回的对象,会:

  1. 破坏索引(索引值基于旧对象计算,修改后索引不一致)
  2. 破坏线程安全(多个 goroutine 同时修改同一对象)

二十一、线程安全保证矩阵

操作 锁类型 保护对象
Add/Update Lock items, index.indices, rv, metrics
DeleteWithObject Lock items, index.indices, rv, metrics
Replace Lock items, index.indices, rv, metrics
Bookmark Lock rv, metrics
Transaction Lock items, index.indices, rv, metrics
AddIndexers Lock index.indexers, index.indices
Get RLock items (只读)
List RLock items (只读)
ListKeys RLock items (只读)
Index/ByIndex/IndexKeys/ListIndexFuncValues RLock index.indices, items (只读)
GetIndexers 无锁 index.indexers (只读,创建后不变)
LastStoreSyncResourceVersion RLock rv

二十二、设计模式总结

# 模式 体现
1 三层分离 cache(门面+KeyFunc) → threadSafeMap(线程安全+RV) → storeIndex(索引)
2 门面模式 cache 委托 threadSafeMap,添加 KeyFunc/TransformFunc
3 选项模式 StoreOption/ThreadSafeStoreOption
4 接口隔离 Store vs Indexer vs ThreadSafeStore vs TransactionStore
5 快速路径优化 updateSingleIndex 单值且未变 → 跳过;getKeysFromIndex 单值 → 不去重
6 空 Set 清理 deleteKeyFromIndex 中 len==0 → delete(index, value)
7 panic 一致性 IndexFunc 失败 → panic(索引不一致比崩溃更危险)
8 RV 追踪 每次写操作更新 RV,Bookmark 单独更新
9 Feature Gate AtomicFIFO 控制 LastStoreSyncResourceVersion 的可见性
10 批量锁优化 Transaction 单次锁获取执行多个操作
11 安全 Delete cache 层用 DeleteWithObject(更新 RV),不用 Delete(不更新 RV)
12 Add=Update threadSafeMap.Add 直接调用 Update(只存最新值)

二十三、源文件导出符号清单

store.go

符号 类型
Store interface
TransactionType type alias
TransactionTypeAdd/Update/Delete constant
Transaction struct
TransactionStore interface
TransactionError struct
KeyFunc type alias
KeyError struct
ExplicitKey type alias
MetaNamespaceKeyFunc function
ObjectToName function
MetaObjectToName function
SplitMetaNamespaceKey function
cache struct (unexported)
StoreOption type alias
WithTransformer function
WithStoreMetrics function
NewStore function
NewIndexer function

thread_safe_store.go

符号 类型
ThreadSafeStore interface
ThreadSafeStoreWithTransaction interface
ThreadSafeStoreTransaction struct
ThreadSafeStoreOption type alias
WithThreadSafeStoreMetrics function
NewThreadSafeStore function

index.go

符号 类型
Indexer interface
IndexFunc type alias
IndexFuncToKeyFuncAdapter function
NamespaceIndex constant
MetaNamespaceIndexFunc function
index type alias (unexported)
Indexers type alias
Indices type alias
相关推荐
sbjdhjd2 小时前
04(上)| k8s中的微服务
微服务·云原生·kubernetes·开源·云计算·excel·kubelet
这个DBA有点耶4 小时前
时序数据库深度对比:2026 年主流 TSDB 架构演进与选型指南
数据库·sql·云原生·架构·运维开发·时序数据库
qq_452396234 小时前
第二篇:《K8s 集群搭建:Minikube、kubeadm、Kind 对比与实操》
容器·kubernetes·kind
小哈里5 小时前
【K8S】OCI标准下的企业级镜像治理:Harbor+Skopeo+Trivy 最佳实践
云原生·容器·kubernetes·harbor·镜像·skopen
花间相见5 小时前
【Kubernetes02】—— 使用 kubeadm 从零搭建 K8s 集群(实操避坑版)
云原生·容器·kubernetes
张小凡vip6 小时前
Kubernetes--secret的简介和使用
云原生·容器·kubernetes
张忠琳7 小时前
【client-go v0.36.1】(Reflector Part 3) Reflector 超深度分析 — watchList 流式初始化
云原生·kubernetes·informer·client-go·reflector
IT策士8 小时前
k8s 常见面试问题
容器·面试·kubernetes
鹤落晴春8 小时前
【K8s】资源配额与访问控制
docker·容器·kubernetes