Store 超深度分析 --- threadSafeMap 核心、索引体系、RV追踪、事务机制
基于
client-go v0.36.1tools/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 返回的是对象的指针,不是深拷贝。如果调用者修改了返回的对象,会:
- 破坏索引(索引值基于旧对象计算,修改后索引不一致)
- 破坏线程安全(多个 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 |