【client-go v0.36.1】(store Part 1)Store 超深度分析 — 模块定位、接口层次、类结构、KeyFunc体系、构造初始化

Store 超深度分析 --- 模块定位、接口层次、类结构、KeyFunc体系、构造初始化

基于 client-go v0.36.1 tools/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 的职责是:

  1. KeyFunc 调用:将对象转换为 key
  2. TransformFunc 调用:在对象入存储前变换
  3. 接口适配:实现 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
相关推荐
heimeiyingwang3 小时前
【架构实战】网关架构设计:微服务的统一入口
微服务·云原生·架构
开发者联盟league4 小时前
使用jenkins pipeline将项目打包运行在k8s上报错kubectl: Permission denied
java·kubernetes·jenkins
成为你的宁宁4 小时前
【Prometheus Operator 监控 K8S集群的Calico 与 Ingress-Nginx 组件】
kubernetes·prometheus
sbjdhjd4 小时前
04 (下) | K8S微服务实战:从 Service 到金丝雀发布
运维·微服务·云原生·kubernetes·开源·云计算·excel
Plastic garden5 小时前
K8s知识(5) Kubernetes 存储 PV
kubernetes
java_cj5 小时前
K8s入门第一课:从零理解Kubernetes核心概念与架构设计
运维·云原生·容器·架构·kubernetes
半亩码田5 小时前
【.NET新特性·第5篇】.NET 9 速览:云原生与性能之年
云原生·.net
Plastic garden5 小时前
K8s知识(4)Kubernetes 存储 volume
云原生·容器·kubernetes
qq_452396235 小时前
第四篇:《Pod:K8s 中最小的部署单元》
云原生·容器·kubernetes