Store 超深度分析 --- 模块定位、接口层次、类结构、KeyFunc体系、构造初始化
基于
client-go v0.36.1tools/cache/store.go(439行) +thread_safe_store.go(552行) +index.go(100行)
一、模块定位
1.1 业务职责
Store 模块是 client-go Informer 机制中的 本地对象缓存层,负责在内存中维护从 API Server 观察到的最新对象状态。它是 DeltaFIFO 的消费者、Controller 的下游存储。
| # | 职责 | 描述 |
|---|---|---|
| 1 | 对象存储 | 以 key→object 映射形式存储最新对象状态 |
| 2 | 线程安全 | 通过 RWMutex 保证并发读写的安全性 |
| 3 | 索引加速 | 通过 IndexFunc → Index 多级映射,按字段值快速查找对象 |
| 4 | ResourceVersion 追踪 | 记录观察到的最新 RV,用于 Watch 断连重续 |
| 5 | 对象变换 | TransformFunc 在入存储前裁剪不需要的字段(内存优化) |
| 6 | 事务批量操作 | Transaction 接口支持在单次锁获取中执行多个写操作 |
| 7 | 指标暴露 | 通过 InformerMetricsProvider 暴露 RV 指标 |
Store 的三种角色
| 角色 | 类型 | 作用 |
|---|---|---|
| 本地缓存 | cache (struct) |
实现 Store/Indexer 接口,被 Controller/SharedInformer 使用 |
| DeltaFIFO 的 knownObjects | Indexer (interface) |
为 DeltaFIFO 的 Delete/Replace/Resync 提供已知对象信息 |
| Lister 的数据源 | Indexer (interface) |
为用户 Controller 提供本地只读查询能力 |
1.2 在系统中的位置
#mermaid-svg-rTdnQGMgEhizOxod{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-rTdnQGMgEhizOxod .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rTdnQGMgEhizOxod .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rTdnQGMgEhizOxod .error-icon{fill:#552222;}#mermaid-svg-rTdnQGMgEhizOxod .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rTdnQGMgEhizOxod .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rTdnQGMgEhizOxod .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rTdnQGMgEhizOxod .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rTdnQGMgEhizOxod .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rTdnQGMgEhizOxod .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rTdnQGMgEhizOxod .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rTdnQGMgEhizOxod .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rTdnQGMgEhizOxod .marker.cross{stroke:#333333;}#mermaid-svg-rTdnQGMgEhizOxod svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rTdnQGMgEhizOxod p{margin:0;}#mermaid-svg-rTdnQGMgEhizOxod .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rTdnQGMgEhizOxod .cluster-label text{fill:#333;}#mermaid-svg-rTdnQGMgEhizOxod .cluster-label span{color:#333;}#mermaid-svg-rTdnQGMgEhizOxod .cluster-label span p{background-color:transparent;}#mermaid-svg-rTdnQGMgEhizOxod .label text,#mermaid-svg-rTdnQGMgEhizOxod span{fill:#333;color:#333;}#mermaid-svg-rTdnQGMgEhizOxod .node rect,#mermaid-svg-rTdnQGMgEhizOxod .node circle,#mermaid-svg-rTdnQGMgEhizOxod .node ellipse,#mermaid-svg-rTdnQGMgEhizOxod .node polygon,#mermaid-svg-rTdnQGMgEhizOxod .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rTdnQGMgEhizOxod .rough-node .label text,#mermaid-svg-rTdnQGMgEhizOxod .node .label text,#mermaid-svg-rTdnQGMgEhizOxod .image-shape .label,#mermaid-svg-rTdnQGMgEhizOxod .icon-shape .label{text-anchor:middle;}#mermaid-svg-rTdnQGMgEhizOxod .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rTdnQGMgEhizOxod .rough-node .label,#mermaid-svg-rTdnQGMgEhizOxod .node .label,#mermaid-svg-rTdnQGMgEhizOxod .image-shape .label,#mermaid-svg-rTdnQGMgEhizOxod .icon-shape .label{text-align:center;}#mermaid-svg-rTdnQGMgEhizOxod .node.clickable{cursor:pointer;}#mermaid-svg-rTdnQGMgEhizOxod .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rTdnQGMgEhizOxod .arrowheadPath{fill:#333333;}#mermaid-svg-rTdnQGMgEhizOxod .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rTdnQGMgEhizOxod .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rTdnQGMgEhizOxod .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rTdnQGMgEhizOxod .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rTdnQGMgEhizOxod .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rTdnQGMgEhizOxod .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rTdnQGMgEhizOxod .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rTdnQGMgEhizOxod .cluster text{fill:#333;}#mermaid-svg-rTdnQGMgEhizOxod .cluster span{color:#333;}#mermaid-svg-rTdnQGMgEhizOxod 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-rTdnQGMgEhizOxod .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rTdnQGMgEhizOxod rect.text{fill:none;stroke-width:0;}#mermaid-svg-rTdnQGMgEhizOxod .icon-shape,#mermaid-svg-rTdnQGMgEhizOxod .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rTdnQGMgEhizOxod .icon-shape p,#mermaid-svg-rTdnQGMgEhizOxod .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rTdnQGMgEhizOxod .icon-shape .label rect,#mermaid-svg-rTdnQGMgEhizOxod .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rTdnQGMgEhizOxod .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rTdnQGMgEhizOxod .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rTdnQGMgEhizOxod :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 上层 API
Store 层 (本模块)
消费者
队列
生产者
Add/Update/Delete
Replace
Pop (Deltas)
Add/Update/Delete
knownObjects
Reflector
(Watch/List)
DeltaFIFO
Controller
(processLoop)
handleDeltas()
cache
(Store + Indexer)
threadSafeMap
(ThreadSafeStore)
storeIndex
(索引管理)
GenericLister
(用户查询)
用户 Controller
二、接口层次与完整类图
2.1 接口继承关系
#mermaid-svg-GPL3XEky7Wo2ESxl{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-GPL3XEky7Wo2ESxl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GPL3XEky7Wo2ESxl .error-icon{fill:#552222;}#mermaid-svg-GPL3XEky7Wo2ESxl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GPL3XEky7Wo2ESxl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GPL3XEky7Wo2ESxl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GPL3XEky7Wo2ESxl .marker.cross{stroke:#333333;}#mermaid-svg-GPL3XEky7Wo2ESxl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GPL3XEky7Wo2ESxl p{margin:0;}#mermaid-svg-GPL3XEky7Wo2ESxl g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-GPL3XEky7Wo2ESxl g.classGroup text .title{font-weight:bolder;}#mermaid-svg-GPL3XEky7Wo2ESxl .cluster-label text{fill:#333;}#mermaid-svg-GPL3XEky7Wo2ESxl .cluster-label span{color:#333;}#mermaid-svg-GPL3XEky7Wo2ESxl .cluster-label span p{background-color:transparent;}#mermaid-svg-GPL3XEky7Wo2ESxl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GPL3XEky7Wo2ESxl .cluster text{fill:#333;}#mermaid-svg-GPL3XEky7Wo2ESxl .cluster span{color:#333;}#mermaid-svg-GPL3XEky7Wo2ESxl .nodeLabel,#mermaid-svg-GPL3XEky7Wo2ESxl .edgeLabel{color:#131300;}#mermaid-svg-GPL3XEky7Wo2ESxl .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-GPL3XEky7Wo2ESxl .label text{fill:#131300;}#mermaid-svg-GPL3XEky7Wo2ESxl .labelBkg{background:#ECECFF;}#mermaid-svg-GPL3XEky7Wo2ESxl .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-GPL3XEky7Wo2ESxl .classTitle{font-weight:bolder;}#mermaid-svg-GPL3XEky7Wo2ESxl .node rect,#mermaid-svg-GPL3XEky7Wo2ESxl .node circle,#mermaid-svg-GPL3XEky7Wo2ESxl .node ellipse,#mermaid-svg-GPL3XEky7Wo2ESxl .node polygon,#mermaid-svg-GPL3XEky7Wo2ESxl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GPL3XEky7Wo2ESxl .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl g.clickable{cursor:pointer;}#mermaid-svg-GPL3XEky7Wo2ESxl g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-GPL3XEky7Wo2ESxl g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-GPL3XEky7Wo2ESxl .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-GPL3XEky7Wo2ESxl .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-GPL3XEky7Wo2ESxl .dashed-line{stroke-dasharray:3;}#mermaid-svg-GPL3XEky7Wo2ESxl .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-GPL3XEky7Wo2ESxl #compositionStart,#mermaid-svg-GPL3XEky7Wo2ESxl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #compositionEnd,#mermaid-svg-GPL3XEky7Wo2ESxl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #dependencyStart,#mermaid-svg-GPL3XEky7Wo2ESxl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #dependencyStart,#mermaid-svg-GPL3XEky7Wo2ESxl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #extensionStart,#mermaid-svg-GPL3XEky7Wo2ESxl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #extensionEnd,#mermaid-svg-GPL3XEky7Wo2ESxl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #aggregationStart,#mermaid-svg-GPL3XEky7Wo2ESxl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #aggregationEnd,#mermaid-svg-GPL3XEky7Wo2ESxl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #lollipopStart,#mermaid-svg-GPL3XEky7Wo2ESxl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl #lollipopEnd,#mermaid-svg-GPL3XEky7Wo2ESxl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GPL3XEky7Wo2ESxl .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-GPL3XEky7Wo2ESxl .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GPL3XEky7Wo2ESxl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GPL3XEky7Wo2ESxl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GPL3XEky7Wo2ESxl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} extends
implements
implements
implements (conditional)
implements
implements
cacheStorage
index
<<interface>>
Store
+Add(obj) : error
+Update(obj) : error
+Delete(obj) : error
+List() : \[\]interface
+ListKeys() : \[\]string
+LastStoreSyncResourceVersion() : string
+Bookmark(rv string)
+Get(obj)(item, exists, err)
+GetByKey(key)(item, exists, err)
+Replace(list, rv) : error
+Resync() : error
<<interface>>
Indexer
+Index(indexName, obj)(\[\]interface, error)
+IndexKeys(indexName, indexedValue)(\[\]string, error)
+ListIndexFuncValues(indexName) : \[\]string
+ByIndex(indexName, indexedValue)(\[\]interface, error)
+GetIndexers() : Indexers
+AddIndexers(newIndexers) : error
<<interface>>
TransactionStore
+Transaction(txns ...Transaction) : *TransactionError
<<interface>>
ThreadSafeStore
+Add(key, obj)
+Update(key, obj)
+Delete(key)
+DeleteWithObject(key, obj)
+Get(key)(item, exists)
+List() : \[\]interface
+ListKeys() : \[\]string
+Replace(items, rv)
+Index(indexName, obj)(\[\]interface, error)
+IndexKeys(indexName, indexedValue)(\[\]string, error)
+Bookmark(rv string)
+LastStoreSyncResourceVersion() : string
+ListIndexFuncValues(name) : \[\]string
+ByIndex(indexName, indexedValue)(\[\]interface, error)
+GetIndexers() : Indexers
+AddIndexers(newIndexers) : error
+Resync() : error
<<interface>>
ThreadSafeStoreWithTransaction
+Transaction(fns ...ThreadSafeStoreTransaction)
cache
-cacheStorage ThreadSafeStore
-keyFunc KeyFunc
-transformer TransformFunc
-identifier InformerNameAndResource
-metrics InformerMetricsProvider
threadSafeMap
-lock sync.RWMutex
-items mapstringinterface
-index *storeIndex
-rv string
-metrics *storeMetrics
storeIndex
-indexers Indexers
-indices Indices
2.2 三层架构:cache → threadSafeMap → storeIndex
#mermaid-svg-ifV0Sfzes3NKRf5L{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-ifV0Sfzes3NKRf5L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ifV0Sfzes3NKRf5L .error-icon{fill:#552222;}#mermaid-svg-ifV0Sfzes3NKRf5L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ifV0Sfzes3NKRf5L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ifV0Sfzes3NKRf5L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ifV0Sfzes3NKRf5L .marker.cross{stroke:#333333;}#mermaid-svg-ifV0Sfzes3NKRf5L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ifV0Sfzes3NKRf5L p{margin:0;}#mermaid-svg-ifV0Sfzes3NKRf5L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L .cluster-label text{fill:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L .cluster-label span{color:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L .cluster-label span p{background-color:transparent;}#mermaid-svg-ifV0Sfzes3NKRf5L .label text,#mermaid-svg-ifV0Sfzes3NKRf5L span{fill:#333;color:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L .node rect,#mermaid-svg-ifV0Sfzes3NKRf5L .node circle,#mermaid-svg-ifV0Sfzes3NKRf5L .node ellipse,#mermaid-svg-ifV0Sfzes3NKRf5L .node polygon,#mermaid-svg-ifV0Sfzes3NKRf5L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ifV0Sfzes3NKRf5L .rough-node .label text,#mermaid-svg-ifV0Sfzes3NKRf5L .node .label text,#mermaid-svg-ifV0Sfzes3NKRf5L .image-shape .label,#mermaid-svg-ifV0Sfzes3NKRf5L .icon-shape .label{text-anchor:middle;}#mermaid-svg-ifV0Sfzes3NKRf5L .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ifV0Sfzes3NKRf5L .rough-node .label,#mermaid-svg-ifV0Sfzes3NKRf5L .node .label,#mermaid-svg-ifV0Sfzes3NKRf5L .image-shape .label,#mermaid-svg-ifV0Sfzes3NKRf5L .icon-shape .label{text-align:center;}#mermaid-svg-ifV0Sfzes3NKRf5L .node.clickable{cursor:pointer;}#mermaid-svg-ifV0Sfzes3NKRf5L .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ifV0Sfzes3NKRf5L .arrowheadPath{fill:#333333;}#mermaid-svg-ifV0Sfzes3NKRf5L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ifV0Sfzes3NKRf5L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ifV0Sfzes3NKRf5L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ifV0Sfzes3NKRf5L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ifV0Sfzes3NKRf5L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ifV0Sfzes3NKRf5L .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ifV0Sfzes3NKRf5L .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ifV0Sfzes3NKRf5L .cluster text{fill:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L .cluster span{color:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L 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-ifV0Sfzes3NKRf5L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ifV0Sfzes3NKRf5L rect.text{fill:none;stroke-width:0;}#mermaid-svg-ifV0Sfzes3NKRf5L .icon-shape,#mermaid-svg-ifV0Sfzes3NKRf5L .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ifV0Sfzes3NKRf5L .icon-shape p,#mermaid-svg-ifV0Sfzes3NKRf5L .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ifV0Sfzes3NKRf5L .icon-shape .label rect,#mermaid-svg-ifV0Sfzes3NKRf5L .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ifV0Sfzes3NKRf5L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ifV0Sfzes3NKRf5L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ifV0Sfzes3NKRf5L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Layer 3: storeIndex (索引层)
Layer 2: threadSafeMap (线程安全层)
Layer 1: cache (门面层)
keyFunc(obj) + transform
keyFunc(obj) + transform
keyFunc(obj)
keyFunc + transform + 构建 map
keyFunc(obj) → GetByKey
keyFunc → keyedTxns
cache.Add(obj)
cache.Update(obj)
cache.Delete(obj)
cache.Replace(list, rv)
cache.Get(obj)
cache.Index(name, obj)
cache.Transaction(txns)
threadSafeMap.Add(key, obj)
threadSafeMap.Update(key, obj)
threadSafeMap.DeleteWithObject(key, obj)
threadSafeMap.Replace(items, rv)
threadSafeMap.Get(key)
threadSafeMap.Index(name, obj)
threadSafeMap.Transaction(txns)
rv 追踪 + metrics 更新
storeIndex.updateIndices(old, new, key)
storeIndex.getKeysFromIndex(name, obj)
storeIndex.getKeysByIndex(name, value)
storeIndex.addKeyToIndex(key, value, idx)
storeIndex.deleteKeyFromIndex(key, value, idx)
storeIndex.reset()
各层职责
| 层 | 类型 | 职责 |
|---|---|---|
| Layer 1 cache | 门面层 | KeyFunc 提取、TransformFunc 变换、接口适配 |
| Layer 2 threadSafeMap | 线程安全层 | RWMutex 保护、items 存储、RV 追踪、metrics 更新 |
| Layer 3 storeIndex | 索引层 | IndexFunc 计算、索引映射维护、快速查询 |
三、Store 接口逐方法解析
3.1 Store 接口完整方法清单
| 方法 | 参数 | 返回值 | 作用 | 线程安全 |
|---|---|---|---|---|
Add |
obj | error | 添加对象到存储 | ✅ |
Update |
obj | error | 更新存储中的对象 | ✅ |
Delete |
obj | error | 从存储删除对象 | ✅ |
List |
--- | \[\]interface{} | 返回所有对象 | ✅ (只读) |
ListKeys |
--- | \[\]string | 返回所有 key | ✅ (只读) |
Get |
obj | (item, exists, err) | 按对象获取存储值 | ✅ (只读) |
GetByKey |
key | (item, exists, err) | 按 key 获取存储值 | ✅ (只读) |
Replace |
list, rv | error | 替换全部内容 | ✅ |
Resync |
--- | error | 重新同步(Store 中无意义) | ✅ |
LastStoreSyncResourceVersion |
--- | string | 获取最新观察到的 RV | ✅ |
Bookmark |
rv | --- | 更新 RV(不改变对象) | ✅ |
3.2 Indexer 扩展接口
| 方法 | 参数 | 返回值 | 作用 |
|---|---|---|---|
Index |
indexName, obj | (\[\]interface{}, error) | 按索引函数查找匹配对象 |
IndexKeys |
indexName, indexedValue | (\[\]string, error) | 按索引值查找 key |
ListIndexFuncValues |
indexName | \[\]string | 列出索引的所有值 |
ByIndex |
indexName, indexedValue | (\[\]interface{}, error) | 按索引名+值精确查找 |
GetIndexers |
--- | Indexers | 获取所有索引函数 |
AddIndexers |
newIndexers | error | 动态添加索引函数 |
3.3 三种字符串的概念区分
源码注释明确指出 Indexer 中有三种字符串:
| 类型 | 例子 | 含义 |
|---|---|---|
| storage key | "default/nginx-pod" |
Store 中的唯一标识,由 KeyFunc 生成 |
| index name | "namespace" |
索引的名称,标识一种 IndexFunc |
| indexed value | "default" |
IndexFunc 对对象计算出的值,作为索引映射的 key |
#mermaid-svg-l28JuuMe9XHBriez{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-l28JuuMe9XHBriez .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-l28JuuMe9XHBriez .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-l28JuuMe9XHBriez .error-icon{fill:#552222;}#mermaid-svg-l28JuuMe9XHBriez .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-l28JuuMe9XHBriez .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-l28JuuMe9XHBriez .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-l28JuuMe9XHBriez .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-l28JuuMe9XHBriez .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-l28JuuMe9XHBriez .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-l28JuuMe9XHBriez .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-l28JuuMe9XHBriez .marker{fill:#333333;stroke:#333333;}#mermaid-svg-l28JuuMe9XHBriez .marker.cross{stroke:#333333;}#mermaid-svg-l28JuuMe9XHBriez svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-l28JuuMe9XHBriez p{margin:0;}#mermaid-svg-l28JuuMe9XHBriez .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-l28JuuMe9XHBriez .cluster-label text{fill:#333;}#mermaid-svg-l28JuuMe9XHBriez .cluster-label span{color:#333;}#mermaid-svg-l28JuuMe9XHBriez .cluster-label span p{background-color:transparent;}#mermaid-svg-l28JuuMe9XHBriez .label text,#mermaid-svg-l28JuuMe9XHBriez span{fill:#333;color:#333;}#mermaid-svg-l28JuuMe9XHBriez .node rect,#mermaid-svg-l28JuuMe9XHBriez .node circle,#mermaid-svg-l28JuuMe9XHBriez .node ellipse,#mermaid-svg-l28JuuMe9XHBriez .node polygon,#mermaid-svg-l28JuuMe9XHBriez .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-l28JuuMe9XHBriez .rough-node .label text,#mermaid-svg-l28JuuMe9XHBriez .node .label text,#mermaid-svg-l28JuuMe9XHBriez .image-shape .label,#mermaid-svg-l28JuuMe9XHBriez .icon-shape .label{text-anchor:middle;}#mermaid-svg-l28JuuMe9XHBriez .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-l28JuuMe9XHBriez .rough-node .label,#mermaid-svg-l28JuuMe9XHBriez .node .label,#mermaid-svg-l28JuuMe9XHBriez .image-shape .label,#mermaid-svg-l28JuuMe9XHBriez .icon-shape .label{text-align:center;}#mermaid-svg-l28JuuMe9XHBriez .node.clickable{cursor:pointer;}#mermaid-svg-l28JuuMe9XHBriez .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-l28JuuMe9XHBriez .arrowheadPath{fill:#333333;}#mermaid-svg-l28JuuMe9XHBriez .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-l28JuuMe9XHBriez .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-l28JuuMe9XHBriez .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l28JuuMe9XHBriez .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-l28JuuMe9XHBriez .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l28JuuMe9XHBriez .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-l28JuuMe9XHBriez .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-l28JuuMe9XHBriez .cluster text{fill:#333;}#mermaid-svg-l28JuuMe9XHBriez .cluster span{color:#333;}#mermaid-svg-l28JuuMe9XHBriez 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-l28JuuMe9XHBriez .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-l28JuuMe9XHBriez rect.text{fill:none;stroke-width:0;}#mermaid-svg-l28JuuMe9XHBriez .icon-shape,#mermaid-svg-l28JuuMe9XHBriez .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l28JuuMe9XHBriez .icon-shape p,#mermaid-svg-l28JuuMe9XHBriez .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-l28JuuMe9XHBriez .icon-shape .label rect,#mermaid-svg-l28JuuMe9XHBriez .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l28JuuMe9XHBriez .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-l28JuuMe9XHBriez .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-l28JuuMe9XHBriez :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Object
(Pod: nginx)
KeyFunc
→ 'default/nginx'
IndexFunc('namespace')
→ 'default'
Storage Key
'default/nginx'
Index Name
'namespace'
Indexed Value
'default'
items map
indices map
四、KeyFunc 体系
4.1 KeyFunc 类型
go
type KeyFunc func(obj interface{}) (string, error)
设计约束 :KeyFunc 必须确定性(deterministic)------同一对象必须始终返回相同的 Key。
4.2 MetaNamespaceKeyFunc --- 默认 KeyFunc
go
func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
// ─── 场景1:显式 Key ───
// 当调用者已有 Key 但没有对象时(如 Delete 传入 DeletedFinalStateUnknown.Key)
if key, ok := obj.(ExplicitKey); ok {
return string(key), nil // 直接使用显式 Key
}
// ─── 场景2:正常对象 ───
objName, err := ObjectToName(obj)
if err != nil {
return "", err // 对象没有 meta → 无法提取 Key
}
return objName.String(), nil // namespace/name 或 name
}
Key 格式规则
| 场景 | namespace | name | 生成的 Key |
|---|---|---|---|
| Core Group (无 namespace) | "" |
"node-1" |
"node-1" |
| Named Group (有 namespace) | "default" |
"nginx-pod" |
"default/nginx-pod" |
| Cluster-scoped CRD | "" |
"my-cluster-resource" |
"my-cluster-resource" |
4.3 ObjectToName / MetaObjectToName
go
func ObjectToName(obj interface{}) (ObjectName, error) {
meta, err := meta.Accessor(obj) // 提取 metav1.Object 接口
if err != nil {
return ObjectName{}, fmt.Errorf("object has no meta: %v", err)
}
return MetaObjectToName(meta), nil
}
func MetaObjectToName(obj metav1.Object) ObjectName {
if len(obj.GetNamespace()) > 0 {
return ObjectName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
}
return ObjectName{Namespace: "", Name: obj.GetName()}
}
4.4 SplitMetaNamespaceKey --- Key 反向解析
go
func SplitMetaNamespaceKey(key string) (namespace, name string, err error) {
parts := strings.Split(key, "/")
switch len(parts) {
case 1:
return "", parts[0], nil // name only → 无 namespace
case 2:
return parts[0], parts[1], nil // namespace/name
}
return "", "", fmt.Errorf("unexpected key format: %q", key)
// 不支持 3+ 段的 Key → 格式错误
}
4.5 ExplicitKey --- 显式 Key 传递
go
type ExplicitKey string
设计意图 :某些场景下调用者只有 Key 没有完整对象(如删除操作只传 Key)。ExplicitKey 让 KeyFunc 直接返回传入的 Key,跳过 meta.Accessor 提取。
#mermaid-svg-soBQLMA3uVkOH1Fh{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-soBQLMA3uVkOH1Fh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-soBQLMA3uVkOH1Fh .error-icon{fill:#552222;}#mermaid-svg-soBQLMA3uVkOH1Fh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-soBQLMA3uVkOH1Fh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-soBQLMA3uVkOH1Fh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-soBQLMA3uVkOH1Fh .marker.cross{stroke:#333333;}#mermaid-svg-soBQLMA3uVkOH1Fh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-soBQLMA3uVkOH1Fh p{margin:0;}#mermaid-svg-soBQLMA3uVkOH1Fh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh .cluster-label text{fill:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh .cluster-label span{color:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh .cluster-label span p{background-color:transparent;}#mermaid-svg-soBQLMA3uVkOH1Fh .label text,#mermaid-svg-soBQLMA3uVkOH1Fh span{fill:#333;color:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh .node rect,#mermaid-svg-soBQLMA3uVkOH1Fh .node circle,#mermaid-svg-soBQLMA3uVkOH1Fh .node ellipse,#mermaid-svg-soBQLMA3uVkOH1Fh .node polygon,#mermaid-svg-soBQLMA3uVkOH1Fh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-soBQLMA3uVkOH1Fh .rough-node .label text,#mermaid-svg-soBQLMA3uVkOH1Fh .node .label text,#mermaid-svg-soBQLMA3uVkOH1Fh .image-shape .label,#mermaid-svg-soBQLMA3uVkOH1Fh .icon-shape .label{text-anchor:middle;}#mermaid-svg-soBQLMA3uVkOH1Fh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-soBQLMA3uVkOH1Fh .rough-node .label,#mermaid-svg-soBQLMA3uVkOH1Fh .node .label,#mermaid-svg-soBQLMA3uVkOH1Fh .image-shape .label,#mermaid-svg-soBQLMA3uVkOH1Fh .icon-shape .label{text-align:center;}#mermaid-svg-soBQLMA3uVkOH1Fh .node.clickable{cursor:pointer;}#mermaid-svg-soBQLMA3uVkOH1Fh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-soBQLMA3uVkOH1Fh .arrowheadPath{fill:#333333;}#mermaid-svg-soBQLMA3uVkOH1Fh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-soBQLMA3uVkOH1Fh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-soBQLMA3uVkOH1Fh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-soBQLMA3uVkOH1Fh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-soBQLMA3uVkOH1Fh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-soBQLMA3uVkOH1Fh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-soBQLMA3uVkOH1Fh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-soBQLMA3uVkOH1Fh .cluster text{fill:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh .cluster span{color:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh 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-soBQLMA3uVkOH1Fh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-soBQLMA3uVkOH1Fh rect.text{fill:none;stroke-width:0;}#mermaid-svg-soBQLMA3uVkOH1Fh .icon-shape,#mermaid-svg-soBQLMA3uVkOH1Fh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-soBQLMA3uVkOH1Fh .icon-shape p,#mermaid-svg-soBQLMA3uVkOH1Fh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-soBQLMA3uVkOH1Fh .icon-shape .label rect,#mermaid-svg-soBQLMA3uVkOH1Fh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-soBQLMA3uVkOH1Fh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-soBQLMA3uVkOH1Fh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-soBQLMA3uVkOH1Fh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ExplicitKey
其他
MetaNamespaceKeyFunc(obj)
obj 类型?
obj.(ExplicitKey)
return string(key)
ObjectToName(obj)
meta.Accessor(obj)
MetaObjectToName(meta)
namespace/name
或 name
五、cache 结构体逐字段解析
5.1 cache 结构体
go
type cache struct {
cacheStorage ThreadSafeStore // 线程安全的底层存储
// 实际类型为 *threadSafeMap
// 承担所有并发安全责任
keyFunc KeyFunc // 对象 → Key 的提取函数
// cache 层负责调用,threadSafeMap 层只接收 key
transformer TransformFunc // 入存储前的对象变换函数
// 为 nil 则不变换
// 用于裁剪不需要的字段减少内存
identifier InformerNameAndResource // Informer 标识(用于 metrics)
// 如 "sharedInformerFactory/default/pods"
metrics InformerMetricsProvider // Metrics 提供者
// 用于创建 storeResourceVersion Gauge
}
设计意图:cache 是门面(Facade)
cache 不直接存储数据,所有存储操作委托给 cacheStorage(threadSafeMap)。cache 的职责是:
- KeyFunc 调用:将对象转换为 key
- TransformFunc 调用:在对象入存储前变换
- 接口适配:实现 Store/Indexer/TransactionStore 接口
5.2 编译时接口检查
go
var _ Store = &cache{} // cache 实现了 Store 接口
六、cache 核心方法逐行解析
6.1 Add
go
func (c *cache) Add(obj interface{}) error {
// ─── Step 1: 提取 Key ───
key, err := c.keyFunc(obj)
if err != nil {
return KeyError{obj, err} // Key 提取失败 → 包装为 KeyError
}
// ─── Step 2: TransformFunc 变换 ───
if c.transformer != nil {
obj, err = c.transformer(obj)
if err != nil {
return fmt.Errorf("transforming: %w", err) // 变换失败
}
}
// ─── Step 3: 委托给 cacheStorage ───
c.cacheStorage.Add(key, obj) // threadSafeMap.Add → Update
return nil
}
6.2 Update
go
func (c *cache) Update(obj interface{}) error {
// 与 Add 完全相同的逻辑,只是委托调用 Update 而非 Add
key, err := c.keyFunc(obj)
if err != nil { return KeyError{obj, err} }
if c.transformer != nil {
obj, err = c.transformer(obj)
if err != nil { return fmt.Errorf("transforming: %w", err) }
}
c.cacheStorage.Update(key, obj)
return nil
}
注意 :threadSafeMap.Add 实际上也调用 Update(c.Update(key, obj)),所以 Add 和 Update 在底层完全等价。
6.3 Delete
go
func (c *cache) Delete(obj interface{}) error {
key, err := c.keyFunc(obj)
if err != nil { return KeyError{obj, err} }
// ─── 注意:Delete 不调用 transformer ───
// 原因:删除的对象不需要变换,直接传递给 cacheStorage
// DeleteWithObject 接收 key + obj,obj 用于提取 RV 更新指标
c.cacheStorage.DeleteWithObject(key, obj) // 不是 Delete(key)!
return nil
}
为什么用 DeleteWithObject 而不是 Delete? DeleteWithObject 从 obj 中提取 ResourceVersion 来更新指标。如果只用 Delete(key),RV 不会更新,可能导致 store 的 RV 落后于实际观察值。
6.4 Get
go
func (c *cache) Get(obj interface{}) (item interface{}, exists bool, err error) {
key, err := c.keyFunc(obj) // 先提取 Key
if err != nil {
return nil, false, KeyError{obj, err}
}
return c.GetByKey(key) // 再按 Key 查询
}
6.5 GetByKey
go
func (c *cache) GetByKey(key string) (item interface{}, exists bool, err error) {
item, exists = c.cacheStorage.Get(key) // 直接委托
return item, exists, nil // err 永远为 nil
}
注意:GetByKey 的 err 永远为 nil。这是因为 threadSafeMap.Get 只做 map 查找,不会出错。
6.6 Replace
go
func (c *cache) Replace(list []interface{}, resourceVersion string) error {
// ─── Step 1: 构建 key→object 映射 ───
items := make(map[string]interface{}, len(list))
for _, item := range list {
key, err := c.keyFunc(item)
if err != nil {
return KeyError{item, err}
}
// ─── Transform 变换 ───
if c.transformer != nil {
item, err = c.transformer(item)
if err != nil {
return fmt.Errorf("transforming: %w", err)
}
}
items[key] = item // 如果有重复 key,后者覆盖前者
}
// ─── Step 2: 委托给 cacheStorage ───
c.cacheStorage.Replace(items, resourceVersion)
return nil
}
与 DeltaFIFO.Replace 的区别:
| 维度 | cache.Replace | DeltaFIFO.Replace |
|---|---|---|
| 操作方式 | 原子替换 items | 逐个入队 Delta |
| 删除检测 | 无 | 检测消失对象生成墓碑 |
| 索引更新 | reset + 重建 | 不涉及(无索引) |
| RV 更新 | 直接设置 | 忽略(传 _) |
6.7 Resync
go
func (c *cache) Resync() error {
return nil // Store 的 Resync 无意义
// 原因:Store 只保存最新状态,没有"过期"概念
// DeltaFIFO 的 Resync 有意义:将已知对象重新入队
}
6.8 Transaction
go
func (c *cache) Transaction(txns ...Transaction) *TransactionError {
// ─── Step 1: 检查底层是否支持事务 ───
txnStore, ok := c.cacheStorage.(ThreadSafeStoreWithTransaction)
if !ok {
// 底层不支持事务 → 返回错误
return &TransactionError{
TotalTransactions: len(txns),
Errors: []error{errors.New("transaction not supported")},
}
}
// ─── Step 2: 将 Transaction 转换为 ThreadSafeStoreTransaction ───
// 需要为每个 Transaction 计算 Key
keyedTxns := make([]ThreadSafeStoreTransaction, 0, len(txns))
successfulIndices := make([]int, 0, len(txns))
errs := make([]error, 0)
for i := range txns {
txn := txns[i]
key, err := c.keyFunc(txn.Object)
if err != nil {
errs = append(errs, KeyError{txn.Object, err}) // Key 失败 → 记录错误
continue
}
successfulIndices = append(successfulIndices, i)
keyedTxns = append(keyedTxns, ThreadSafeStoreTransaction{txn, key})
}
// ─── Step 3: 批量执行 ───
// 整个 batch 在一次锁获取中执行
txnStore.Transaction(keyedTxns...)
if len(errs) > 0 {
return &TransactionError{
SuccessfulIndices: successfulIndices,
TotalTransactions: len(txns),
Errors: errs,
}
}
return nil
}
七、ThreadSafeStore 接口
7.1 ThreadSafeStore vs Store
| 维度 | Store | ThreadSafeStore |
|---|---|---|
| Key 来源 | 自动通过 KeyFunc 从 obj 提取 | 调用者显式传入 key |
| 适用层 | cache(门面层) | threadSafeMap(底层) |
| KeyFunc | cache 持有 | 无(不需要) |
| TransformFunc | cache 调用 | 无(cache 已调用) |
| Delete | Delete(obj) |
Delete(key) + DeleteWithObject(key, obj) |
设计意图:分离 Key 提取与存储操作。cache 负责 Key 提取和变换,threadSafeMap 只做纯存储操作。
八、构造函数与选项模式
8.1 NewStore
go
func NewStore(keyFunc KeyFunc, opts ...StoreOption) Store {
c := &cache{
keyFunc: keyFunc,
}
// 应用选项
for _, opt := range opts {
opt(c)
}
// 构建 ThreadSafeStore 选项
threadSafeOpts := []ThreadSafeStoreOption{}
if c.metrics != nil {
threadSafeOpts = append(threadSafeOpts,
WithThreadSafeStoreMetrics(c.identifier, c.metrics))
}
// 创建底层存储
c.cacheStorage = NewThreadSafeStore(Indexers{}, Indices{}, threadSafeOpts...)
// 空的 Indexers 和 Indices → 无索引的简单 Store
return c
}
8.2 NewIndexer
go
func NewIndexer(keyFunc KeyFunc, indexers Indexers, opts ...StoreOption) Indexer {
c := &cache{
keyFunc: keyFunc,
}
for _, opt := range opts {
opt(c)
}
threadSafeOpts := []ThreadSafeStoreOption{}
if c.metrics != nil {
threadSafeOpts = append(threadSafeOpts,
WithThreadSafeStoreMetrics(c.identifier, c.metrics))
}
// ─── 与 NewStore 唯一区别:传入 indexers ───
c.cacheStorage = NewThreadSafeStore(indexers, Indices{}, threadSafeOpts...)
// Indices 初始为空,会在 Add/Update 时自动构建
return c
}
8.3 StoreOption --- 函数选项模式
go
type StoreOption = func(*cache)
// WithTransformer --- 设置对象变换函数
func WithTransformer(transformer TransformFunc) StoreOption {
return func(c *cache) {
c.transformer = transformer
}
}
// WithStoreMetrics --- 设置指标
func WithStoreMetrics(identifier InformerNameAndResource, metrics InformerMetricsProvider) StoreOption {
return func(c *cache) {
c.identifier = identifier
c.metrics = metrics
}
}
8.4 构造流程图
#mermaid-svg-LPahvZuFCZ5thabb{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-LPahvZuFCZ5thabb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LPahvZuFCZ5thabb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LPahvZuFCZ5thabb .error-icon{fill:#552222;}#mermaid-svg-LPahvZuFCZ5thabb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LPahvZuFCZ5thabb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LPahvZuFCZ5thabb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LPahvZuFCZ5thabb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LPahvZuFCZ5thabb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LPahvZuFCZ5thabb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LPahvZuFCZ5thabb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LPahvZuFCZ5thabb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LPahvZuFCZ5thabb .marker.cross{stroke:#333333;}#mermaid-svg-LPahvZuFCZ5thabb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LPahvZuFCZ5thabb p{margin:0;}#mermaid-svg-LPahvZuFCZ5thabb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LPahvZuFCZ5thabb .cluster-label text{fill:#333;}#mermaid-svg-LPahvZuFCZ5thabb .cluster-label span{color:#333;}#mermaid-svg-LPahvZuFCZ5thabb .cluster-label span p{background-color:transparent;}#mermaid-svg-LPahvZuFCZ5thabb .label text,#mermaid-svg-LPahvZuFCZ5thabb span{fill:#333;color:#333;}#mermaid-svg-LPahvZuFCZ5thabb .node rect,#mermaid-svg-LPahvZuFCZ5thabb .node circle,#mermaid-svg-LPahvZuFCZ5thabb .node ellipse,#mermaid-svg-LPahvZuFCZ5thabb .node polygon,#mermaid-svg-LPahvZuFCZ5thabb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LPahvZuFCZ5thabb .rough-node .label text,#mermaid-svg-LPahvZuFCZ5thabb .node .label text,#mermaid-svg-LPahvZuFCZ5thabb .image-shape .label,#mermaid-svg-LPahvZuFCZ5thabb .icon-shape .label{text-anchor:middle;}#mermaid-svg-LPahvZuFCZ5thabb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LPahvZuFCZ5thabb .rough-node .label,#mermaid-svg-LPahvZuFCZ5thabb .node .label,#mermaid-svg-LPahvZuFCZ5thabb .image-shape .label,#mermaid-svg-LPahvZuFCZ5thabb .icon-shape .label{text-align:center;}#mermaid-svg-LPahvZuFCZ5thabb .node.clickable{cursor:pointer;}#mermaid-svg-LPahvZuFCZ5thabb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LPahvZuFCZ5thabb .arrowheadPath{fill:#333333;}#mermaid-svg-LPahvZuFCZ5thabb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LPahvZuFCZ5thabb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LPahvZuFCZ5thabb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LPahvZuFCZ5thabb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LPahvZuFCZ5thabb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LPahvZuFCZ5thabb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LPahvZuFCZ5thabb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LPahvZuFCZ5thabb .cluster text{fill:#333;}#mermaid-svg-LPahvZuFCZ5thabb .cluster span{color:#333;}#mermaid-svg-LPahvZuFCZ5thabb 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-LPahvZuFCZ5thabb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LPahvZuFCZ5thabb rect.text{fill:none;stroke-width:0;}#mermaid-svg-LPahvZuFCZ5thabb .icon-shape,#mermaid-svg-LPahvZuFCZ5thabb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LPahvZuFCZ5thabb .icon-shape p,#mermaid-svg-LPahvZuFCZ5thabb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LPahvZuFCZ5thabb .icon-shape .label rect,#mermaid-svg-LPahvZuFCZ5thabb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LPahvZuFCZ5thabb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LPahvZuFCZ5thabb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LPahvZuFCZ5thabb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
NewStore(keyFunc, opts...)
NewIndexer(keyFunc, indexers, opts...)
c := &cache{keyFunc: keyFunc}
for opt := range opts { opt(c) }
构建 ThreadSafeStore 选项
c.metrics != nil?
WithThreadSafeStoreMetrics(...)
NewThreadSafeStore(...)
返回 Store (无索引)
返回 Indexer (有索引)
c := &cache{keyFunc: keyFunc}
for opt := range opts { opt(c) }
构建 ThreadSafeStore 选项
NewThreadSafeStore(indexers, Indices{}, ...)
九、TransactionStore 与 TransactionError
9.1 Transaction 类型
go
type TransactionType string
const (
TransactionTypeAdd TransactionType = "Add"
TransactionTypeUpdate TransactionType = "Update"
TransactionTypeDelete TransactionType = "Delete"
)
type Transaction struct {
Object interface{} // 操作的对象
Type TransactionType // 操作类型
}
9.2 TransactionError
go
type TransactionError struct {
SuccessfulIndices []int // 成功执行的事务索引
TotalTransactions int // 总事务数
Errors []error // 失败的错误列表
}
func (t *TransactionError) Error() string {
return fmt.Sprintf("failed to execute (%d/%d) transactions failed due to: %v",
t.TotalTransactions-len(t.SuccessfulIndices), t.TotalTransactions, t.Errors)
}
设计意图 :部分成功的事务仍会执行(KeyFunc 成功的会执行,KeyFunc 失败的跳过)。SuccessfulIndices 让调用者知道哪些事务成功,哪些失败。
#mermaid-svg-EcduOaIYOI5KgpBl{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-EcduOaIYOI5KgpBl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EcduOaIYOI5KgpBl .error-icon{fill:#552222;}#mermaid-svg-EcduOaIYOI5KgpBl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EcduOaIYOI5KgpBl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EcduOaIYOI5KgpBl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EcduOaIYOI5KgpBl .marker.cross{stroke:#333333;}#mermaid-svg-EcduOaIYOI5KgpBl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EcduOaIYOI5KgpBl p{margin:0;}#mermaid-svg-EcduOaIYOI5KgpBl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EcduOaIYOI5KgpBl .cluster-label text{fill:#333;}#mermaid-svg-EcduOaIYOI5KgpBl .cluster-label span{color:#333;}#mermaid-svg-EcduOaIYOI5KgpBl .cluster-label span p{background-color:transparent;}#mermaid-svg-EcduOaIYOI5KgpBl .label text,#mermaid-svg-EcduOaIYOI5KgpBl span{fill:#333;color:#333;}#mermaid-svg-EcduOaIYOI5KgpBl .node rect,#mermaid-svg-EcduOaIYOI5KgpBl .node circle,#mermaid-svg-EcduOaIYOI5KgpBl .node ellipse,#mermaid-svg-EcduOaIYOI5KgpBl .node polygon,#mermaid-svg-EcduOaIYOI5KgpBl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EcduOaIYOI5KgpBl .rough-node .label text,#mermaid-svg-EcduOaIYOI5KgpBl .node .label text,#mermaid-svg-EcduOaIYOI5KgpBl .image-shape .label,#mermaid-svg-EcduOaIYOI5KgpBl .icon-shape .label{text-anchor:middle;}#mermaid-svg-EcduOaIYOI5KgpBl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EcduOaIYOI5KgpBl .rough-node .label,#mermaid-svg-EcduOaIYOI5KgpBl .node .label,#mermaid-svg-EcduOaIYOI5KgpBl .image-shape .label,#mermaid-svg-EcduOaIYOI5KgpBl .icon-shape .label{text-align:center;}#mermaid-svg-EcduOaIYOI5KgpBl .node.clickable{cursor:pointer;}#mermaid-svg-EcduOaIYOI5KgpBl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EcduOaIYOI5KgpBl .arrowheadPath{fill:#333333;}#mermaid-svg-EcduOaIYOI5KgpBl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EcduOaIYOI5KgpBl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EcduOaIYOI5KgpBl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EcduOaIYOI5KgpBl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EcduOaIYOI5KgpBl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EcduOaIYOI5KgpBl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EcduOaIYOI5KgpBl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EcduOaIYOI5KgpBl .cluster text{fill:#333;}#mermaid-svg-EcduOaIYOI5KgpBl .cluster span{color:#333;}#mermaid-svg-EcduOaIYOI5KgpBl 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-EcduOaIYOI5KgpBl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EcduOaIYOI5KgpBl rect.text{fill:none;stroke-width:0;}#mermaid-svg-EcduOaIYOI5KgpBl .icon-shape,#mermaid-svg-EcduOaIYOI5KgpBl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EcduOaIYOI5KgpBl .icon-shape p,#mermaid-svg-EcduOaIYOI5KgpBl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EcduOaIYOI5KgpBl .icon-shape .label rect,#mermaid-svg-EcduOaIYOI5KgpBl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EcduOaIYOI5KgpBl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EcduOaIYOI5KgpBl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EcduOaIYOI5KgpBl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Transaction(txn1, txn2, txn3)
key1 := keyFunc(txn1.Object) → OK
key2 := keyFunc(txn2.Object) → KeyError!
key3 := keyFunc(txn3.Object) → OK
txnStore.Transaction(keyedTxn1, keyedTxn3)
TransactionError{
SuccessfulIndices: 0, 2,
TotalTransactions: 3,
Errors: KeyError(txn2)
}
十、KError 与 ExplicitKey
10.1 KeyError
go
type KeyError struct {
Obj interface{} // 导致错误的对象
Err error // 底层错误
}
func (k KeyError) Error() string {
return fmt.Sprintf("couldn't create key for object %+v: %v", k.Obj, k.Err)
}
func (k KeyError) Unwrap() error {
return k.Err // 支持 errors.Unwrap/is.As 链
}
设计意图 :KeyFunc 失败时包装对象和错误,方便调试。实现 Unwrap() 支持错误链解包。
10.2 IndexFuncToKeyFuncAdapter
go
func IndexFuncToKeyFuncAdapter(indexFunc IndexFunc) KeyFunc {
return func(obj interface{}) (string, error) {
indexKeys, err := indexFunc(obj)
if err != nil { return "", err }
if len(indexKeys) > 1 {
return "", fmt.Errorf("too many keys: %v", indexKeys) // 多值 → 错误
}
if len(indexKeys) == 0 {
return "", fmt.Errorf("unexpected empty indexKeys") // 空值 → 错误
}
return indexKeys[0], nil // 单值 → 适配为 KeyFunc
}
}
警告注释:这只有当 IndexFunc 返回唯一值时才安全。如果 IndexFunc 可能返回多个值,不应使用此适配器。
十一、设计模式总结
| # | 模式 | 体现 |
|---|---|---|
| 1 | 门面模式 | cache 委托 threadSafeMap,添加 KeyFunc/TransformFunc |
| 2 | 三层分离 | cache(门面) → threadSafeMap(线程安全) → storeIndex(索引) |
| 3 | 选项模式 | StoreOption/ThreadSafeStoreOption 函数选项 |
| 4 | 接口隔离 | Store vs Indexer vs ThreadSafeStore vs TransactionStore |
| 5 | 接口组合 | Indexer = Store + Index/ByIndex/GetIndexers/AddIndexers |
| 6 | 委托模式 | cache 委托 cacheStorage,cache 不直接存数据 |
| 7 | 编译时检查 | var _ Store = &cache{} |
| 8 | 空对象 | noopMetric/noopInformerMetricsProvider |
| 9 | 批量操作 | Transaction 接口减少锁获取次数 |
| 10 | Key-Value 分离 | cache 层做 obj→key,threadSafeMap 层只处理 key |