|
| 1 | +Store 是 KV 类型的缓存。 |
| 2 | +Indexer 在 Store 接口的基础上,添加了对象索引功能。索引是索引值和对象 Key 集合的映射表,不同 IndexFunc 为对象产生不同的索引值列表。通过索引值可以快速获取对象的 Key。 |
| 3 | +cache 是实现了 Store 和 Indexer 接口的内置对象,它内部使用了实现 ThreadSafeStore 接口的对象和 KeyFunc 类型的函数。 |
| 4 | +后续创建的 Informer 使用 DeletionHandlingMetaNamespaceKeyFunc 作为 KeyFunc 的 cache 实现对象缓存。 |
| 5 | + |
| 6 | +## Store |
| 7 | + |
| 8 | +Store 是 KV 类型的缓存,用于保存和查询对象。但它没有定义生成对象标识 key 的方式,由具体的实现来定的(见后文分析的 `cache` 对象)。 |
| 9 | + |
| 10 | +// 来源于 k8s.io/client-go/tools/cache/store.go |
| 11 | +type Store interface { |
| 12 | + Add(obj interface{}) error |
| 13 | + Update(obj interface{}) error |
| 14 | + Delete(obj interface{}) error |
| 15 | + List() []interface{} |
| 16 | + ListKeys() []string |
| 17 | + Get(obj interface{}) (item interface{}, exists bool, err error) |
| 18 | + GetByKey(key string) (item interface{}, exists bool, err error) |
| 19 | + |
| 20 | + // 使用传入的对象列表替换 Store 中的对象 |
| 21 | + Replace([]interface{}, string) error |
| 22 | + Resync() error |
| 23 | +} |
| 24 | + |
| 25 | +`NewStore` 函数返回一个实现该接口的 `cache` 类型对象(见后文分析)。 |
| 26 | + |
| 27 | +由于 `Queue` 接口是 `Store` 的超级,所以实现 `Queue` 接口的 `FIFO` 类型(fifo.go)、`DeltaFIFO` 类型(delta_file.go) 也实现了 `Store`接口。(详见: [2.queue-fifo-delta_fifo.md](2.queue-fifo-delta_fifo.md) |
| 28 | + |
| 29 | +## 索引接口 Indexer |
| 30 | +Index 是在 Store 的基础上,添加了索引功能,方便后续按照索引来快速获取(一批)对象。 |
| 31 | + |
| 32 | +// 来源于 k8s.io/client-go/tools/cache/index.go |
| 33 | +type Indexer interface { |
| 34 | + // Index 实现了 Store 的接口 |
| 35 | + Store |
| 36 | + // 返回注册的、名为 indexName 的索引函数 |
| 37 | + // Retrieve list of objects that match on the named indexing function |
| 38 | + Index(indexName string, obj interface{}) ([]interface{}, error) |
| 39 | + // IndexKeys returns the set of keys that match on the named indexing function. |
| 40 | + IndexKeys(indexName, indexKey string) ([]string, error) |
| 41 | + // ListIndexFuncValues returns the list of generated values of an Index func |
| 42 | + ListIndexFuncValues(indexName string) []string |
| 43 | + // ByIndex lists object that match on the named indexing function with the exact key |
| 44 | + ByIndex(indexName, indexKey string) ([]interface{}, error) |
| 45 | + // GetIndexer return the indexers |
| 46 | + GetIndexers() Indexers |
| 47 | + |
| 48 | + // AddIndexers adds more indexers to this store. If you call this after you already have data |
| 49 | + // in the store, the results are undefined. |
| 50 | + AddIndexers(newIndexers Indexers) error |
| 51 | +} |
| 52 | + |
| 53 | +`NewIndexer` 函数返回一个实现该接口的 `cache` 类型对象(见后文分析)。 |
| 54 | + |
| 55 | +## 为对象生成索引值列表的 IndexFunc 和包含命名 IndexFunc 集合的 Indexers |
| 56 | +对象的索引是一个字符串列表,可以使用 `IndexFunc` 类型的函数来获取。 |
| 57 | + |
| 58 | +client-go 中提供了一个该类型的函数 `MetaNamespaceIndexFunc`,它使用对象所在的 `Namespace` 作为索引: |
| 59 | + |
| 60 | + // 来源于 k8s.io/client-go/tools/cache/index.go |
| 61 | + func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) { |
| 62 | + meta, err := meta.Accessor(obj) |
| 63 | + if err != nil { |
| 64 | + return []string{""}, fmt.Errorf("object has no meta: %v", err) |
| 65 | + } |
| 66 | + return []string{meta.GetNamespace()}, nil |
| 67 | + } |
| 68 | + |
| 69 | +同时为该 IndexFunc 定义了一个名称 `NamespaceIndex string = "namespace"`,该名称后续可以作为 `Indexes` 的 map key。 |
| 70 | + |
| 71 | +除了 client-go 预定义的 IndexFunc,开发者也可以定义其它 IndexFunc,并给它们分别命名后保存到 Indexers 中: |
| 72 | + |
| 73 | + // 来源于 k8s.io/client-go/tools/cache/index.go |
| 74 | + // map[索引函数名称]索引函数 |
| 75 | + type Indexers map[string]IndexFunc |
| 76 | + |
| 77 | +对象通过 IndexFunc 可以产生多个索引,而通过 KeyFunc 只能生产一个唯一表示该对象的 key。 |
| 78 | + |
| 79 | + |
| 80 | +## 索引数据结构 Index 和 Indices |
| 81 | + |
| 82 | +前面说过,一个对象通过 `IndexFunc` 可以产生多个索引,所以一个索引匹配一组对象。 |
| 83 | + |
| 84 | +`Index` 是用于保存某个索引匹配的对象 Key 集合。 |
| 85 | + |
| 86 | +`Indices` 是用命名的 IndexFunc 作为 map key 的 Index 集合,它保存各命名函数产生的所有索引,即各索引匹配的一组对象的集合。 |
| 87 | + |
| 88 | + // 来源于 k8s.io/client-go/tools/cache/index.go |
| 89 | + // map[索引字符串]set{对象 Key 集合} |
| 90 | + type Index map[string]sets.String |
| 91 | + |
| 92 | + // 对象的索引。map[索引函数名称]Index |
| 93 | + type Indices map[string]Index |
| 94 | + |
| 95 | + |
| 96 | +调用 AddIndexers() 方法向 Index 添加各种索引函数,进而为 Store 中的对象创建多种索引。 |
| 97 | + |
| 98 | +## 多线程安全的、带有索引的缓存 ThreadSafeStore |
| 99 | + |
| 100 | +`ThreadSafeStore` 通过锁机制,实现多 goroutine 可以并发访问的带有索引(Indexer 接口方法)功能的 K-V 数据库(Store 接口方法)。 |
| 101 | + |
| 102 | +`ThreadSafeStore` 后续用于实现 Indexer 和 Store 接口,所以它的方法集中包含 Store/Indexer 接口中定义的同名方法,但和 Store 接口中的同名方法相比,多了一个 obj key 参数,少了一个 GetByKey 方法。 |
| 103 | + |
| 104 | + // 来源于 k8s.io/client-go/tools/cache/thread_safe_store.go |
| 105 | + type ThreadSafeStore interface { |
| 106 | + // 下面这些方法和 Store 接口方法同名,差别在于多了唯一标识 obj 的 key 参数 |
| 107 | + Add(key string, obj interface{}) |
| 108 | + Update(key string, obj interface{}) |
| 109 | + Delete(key string) |
| 110 | + List() []interface{} |
| 111 | + ListKeys() []string |
| 112 | + Get(key string) (item interface{}, exists bool) // 相比 Store 接口,缺少了 GetByKey(key string) 方法 |
| 113 | + |
| 114 | + Replace(map[string]interface{}, string) |
| 115 | + Resync() error |
| 116 | + |
| 117 | + // 下面这些是 Indexer 的接口方法 |
| 118 | + Index(indexName string, obj interface{}) ([]interface{}, error) |
| 119 | + IndexKeys(indexName, indexKey string) ([]string, error) |
| 120 | + ListIndexFuncValues(name string) []string |
| 121 | + ByIndex(indexName, indexKey string) ([]interface{}, error) |
| 122 | + GetIndexers() Indexers |
| 123 | + AddIndexers(newIndexers Indexers) error |
| 124 | + } |
| 125 | + |
| 126 | +后文会具体分析这些方法的功能。 |
| 127 | + |
| 128 | +`NewThreadSafeStore` 返回一个实现该接口的对象,该对象的类型是 `threadSafeMap`: |
| 129 | + |
| 130 | + // 来源于 k8s.io/client-go/tools/cache/thread_safe_store.go |
| 131 | + func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore { |
| 132 | + return &threadSafeMap{ |
| 133 | + items: map[string]interface{}{}, |
| 134 | + indexers: indexers, |
| 135 | + indices: indices, |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | +`threadSafeMap` 使用内置的 items 保存所有对象。 |
| 140 | + |
| 141 | + // 来源于 k8s.io/client-go/tools/cache/thread_safe_store.go |
| 142 | + // threadSafeMap implements ThreadSafeStore |
| 143 | + type threadSafeMap struct { |
| 144 | + lock sync.RWMutex |
| 145 | + // 对象集合。使用对象的 Key 作为 map key; |
| 146 | + items map[string]interface{} |
| 147 | + // 命名的索引函数集合。map[索引函数名称]索引函数 |
| 148 | + indexers Indexers |
| 149 | + // 对象的索引。map[索引函数名称][索引字符串]set{对象 Key 集合} |
| 150 | + indices Indices |
| 151 | + } |
| 152 | + |
| 153 | +我们看看 `threadSafeMap` 的方法实现: |
| 154 | + |
| 155 | + // 来源于 k8s.io/client-go/tools/cache/thread_safe_store.go |
| 156 | + func (c *threadSafeMap) Add(key string, obj interface{}) { |
| 157 | + c.lock.Lock() |
| 158 | + defer c.lock.Unlock() |
| 159 | + oldObject := c.items[key] |
| 160 | + c.items[key] = obj |
| 161 | + c.updateIndices(oldObject, obj, key) |
| 162 | + } |
| 163 | + |
| 164 | + func (c *threadSafeMap) Update(key string, obj interface{}) { |
| 165 | + c.lock.Lock() |
| 166 | + defer c.lock.Unlock() |
| 167 | + oldObject := c.items[key] |
| 168 | + c.items[key] = obj |
| 169 | + c.updateIndices(oldObject, obj, key) |
| 170 | + } |
| 171 | + |
| 172 | +当 Add/Update 一个 obj 时,先使用传入的 obj 更新缓存,然后调用 `updateIndices` 方法更新索引。 |
| 173 | +`updateIndices` 方法分别使用 indexers 中的索引函数,为对象创建索引值(多个),然后将这些索引以及该对象的 Key 更新到索引中(c.indices)。 |
| 174 | + |
| 175 | + // 来源于 k8s.io/client-go/tools/cache/thread_safe_store.go |
| 176 | + func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) { |
| 177 | + // 从索引中移除老的 obj |
| 178 | + if oldObj != nil { |
| 179 | + c.deleteFromIndices(oldObj, key) |
| 180 | + } |
| 181 | + // 遍历 c.indexers 中的索引函数,调用它们为对象产生索引列表 |
| 182 | + for name, indexFunc := range c.indexers { |
| 183 | + indexValues, err := indexFunc(newObj) |
| 184 | + if err != nil { |
| 185 | + panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err)) |
| 186 | + } |
| 187 | + // 获取当前索引函数创建的索引 |
| 188 | + index := c.indices[name] |
| 189 | + if index == nil { |
| 190 | + index = Index{} |
| 191 | + c.indices[name] = index |
| 192 | + } |
| 193 | + // 将所有索引值和对象 Key 更新到缓存 c.indices 中 |
| 194 | + for _, indexValue := range indexValues { |
| 195 | + set := index[indexValue] |
| 196 | + if set == nil { |
| 197 | + set = sets.String{} |
| 198 | + index[indexValue] = set |
| 199 | + } |
| 200 | + set.Insert(key) |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | +`Delete` 方法的实现: |
| 206 | + |
| 207 | + // 来源于 k8s.io/client-go/tools/cache/thread_safe_store.go |
| 208 | + func (c *threadSafeMap) Delete(key string) { |
| 209 | + c.lock.Lock() |
| 210 | + defer c.lock.Unlock() |
| 211 | + if obj, exists := c.items[key]; exists { |
| 212 | + c.deleteFromIndices(obj, key) |
| 213 | + delete(c.items, key) |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | +先后从索引和内部缓存中删除元素。 `deleteFromIndices` 方法遍历 c.indexers 中的索引函数,为 ojb 计算索引值列表,然后再从缓存 c.indices 的索引值的对象 Key 集合中删除该对象 Key。 |
| 218 | +注意:因为一个索引值可能匹配多个对象,所以不能直接删除 c.indics 的索引值。 |
| 219 | + |
| 220 | +`Replace` 方法使用传入的 items 更新内部缓存,然后重建索引: |
| 221 | + |
| 222 | + func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) { |
| 223 | + c.lock.Lock() |
| 224 | + defer c.lock.Unlock() |
| 225 | + c.items = items |
| 226 | + |
| 227 | + // rebuild any index |
| 228 | + c.indices = Indices{} |
| 229 | + for key, item := range c.items { |
| 230 | + c.updateIndices(nil, item, key) |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | +`ThreadSafeStore` 的其它方法功能介绍: |
| 235 | + |
| 236 | +1. `Index(indexName string, obj interface{}) ([]interface{}, error)` |
| 237 | + indexName 为索引函数名称(下同)。使用对应的索引函数为 obj 创建索引值列表,然后查询索引,返回匹配这些索引值的对象列表(去重)。 |
| 238 | +2. `ByIndex(indexName, indexKey string) ([]interface{}, error)` |
| 239 | + indexKey 为索引值,查询索引,返回这个索引值匹配的对象列表; |
| 240 | +3. `IndexKeys(indexName, indexKey string) ([]string, error)` |
| 241 | + indexKey 为索引值,查询索引,返回这个索引匹配的对象 Key 列表; |
| 242 | +4. `ListIndexFuncValues(indexName string) []string` |
| 243 | + 查询索引,返回 indexName 对应的索引函数创建的所有索引包含的对象 Key 列表; |
| 244 | +5. `GetIndexers() Indexers` |
| 245 | + 返回命名的索引函数集合 `c.indexers` |
| 246 | +6. `AddIndexers(newIndexers Indexers) error` |
| 247 | + 将 nexIndexers 中的命名函数添加到 c.indexers 中。 |
| 248 | + 必须在添加任何对象前调用该方法,否则会出错返回。 |
| 249 | + newIndexers 中的命名方法不能与 c.indexers 中保存的命名冲突,否则出错返回。 |
| 250 | +7. `Resync() error` |
| 251 | + 直接返回。因为 Add/Update/Delete/Replace 方法都会同时更新缓存和索引,两者时刻是同步的。 |
| 252 | + |
| 253 | +## KeyFunc 和实现 Store/Indexer 接口的 cache |
| 254 | + |
| 255 | +在分析 `cache` 类型之前,我们先分析为对象生成唯一标识字符串的函数类型 `KeyFunc`: |
| 256 | + |
| 257 | + // 来源于 k8s.io/client-go/tools/cache/store.go |
| 258 | + // KeyFunc knows how to make a key from an object. Implementations should be deterministic. |
| 259 | + type KeyFunc func(obj interface{}) (string, error) |
| 260 | + |
| 261 | +client-go 提供了两个 KeyFunc 类型函数 `MetaNamespaceKeyFunc` 和 `DeletionHandlingMetaNamespaceKeyFunc` (实际用的最多): |
| 262 | ++ `MetaNamespaceKeyFunc`:提取对象的 `<namespace>/<object-name>` 或 `<object-name>` 作为 Key; |
| 263 | ++ `DeletionHandlingMetaNamespaceKeyFunc`:先检查对象是不是 DeletedFinalStateUnknown 类型,如果是直接返回对象的 Key 字段,否则调用 `MetaNamespaceKeyFunc` |
| 264 | + |
| 265 | + // 来源于 k8s.io/client-go/tools/cache/store.go |
| 266 | + func MetaNamespaceKeyFunc(obj interface{}) (string, error) { |
| 267 | + // 如果对象时字符串,则直接使用它作为 Key |
| 268 | + if key, ok := obj.(ExplicitKey); ok { |
| 269 | + return string(key), nil |
| 270 | + } |
| 271 | + // 否则提取对象的 Meta 信息 |
| 272 | + meta, err := meta.Accessor(obj) |
| 273 | + if err != nil { |
| 274 | + return "", fmt.Errorf("object has no meta: %v", err) |
| 275 | + } |
| 276 | + // 如果对象有 Namespace 则使用 `<namespace>/<object-name>` 作为 key |
| 277 | + // 否则使用 `<object-name>` 作为 key |
| 278 | + if len(meta.GetNamespace()) > 0 { |
| 279 | + return meta.GetNamespace() + "/" + meta.GetName(), nil |
| 280 | + } |
| 281 | + return meta.GetName(), nil |
| 282 | + } |
| 283 | + |
| 284 | + // 来源于 k8s.io/client-go/tools/cache/controller.go |
| 285 | + func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) { |
| 286 | + // DeletedFinalStateUnknown 是封装了删除对象 key 和对象本身的类型,由 DeltaFIFO.Replace() 方法产生 |
| 287 | + if d, ok := obj.(DeletedFinalStateUnknown); ok { |
| 288 | + return d.Key, nil |
| 289 | + } |
| 290 | + return MetaNamespaceKeyFunc(obj) |
| 291 | + } |
| 292 | +与 `MetaNamespaceKeyFunc` 功能相反的是 `SplitMetaNamespaceKey` 函数,它将传入的 Key 分解,返回对应的 namespace 和 obj name。 |
| 293 | + |
| 294 | +然后我们再来分析内置类型 `cache`。 |
| 295 | + |
| 296 | +`cache` 实现了 `Indexer` 和 `Store` 接口,函数 `NewIndexer` 和 `NewStore` 均返回该类型的对象: |
| 297 | + |
| 298 | + // 来源于 k8s.io/client-go/tools/cache/store.go |
| 299 | + // NewIndexer returns an Indexer implemented simply with a map and a lock. |
| 300 | + func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer { |
| 301 | + return &cache{ |
| 302 | + cacheStorage: NewThreadSafeStore(indexers, Indices{}), |
| 303 | + keyFunc: keyFunc, |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + // 来源于 k8s.io/client-go/tools/cache/store.go |
| 308 | + // NewStore returns a Store implemented simply with a map and a lock. |
| 309 | + func NewStore(keyFunc KeyFunc) Store { |
| 310 | + return &cache{ |
| 311 | + cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}), |
| 312 | + keyFunc: keyFunc, |
| 313 | + } |
| 314 | + } |
| 315 | + |
| 316 | +在分析 `ThreadSafeStore` 时提到过,它的缓存相关方法如 `Add/Update/Delete/Get` 都**需要传入**对象的标识字符串 Key。而 `cache` 封装了 `ThreadSafeStore` 和 `KeyFunc` 类型成员,它使用成员 `keyFunc` 函数为添加到 `cacheStorage` 的对象生成标识字符串 Key。以 cache 的 `Add` 方法为例: |
| 317 | + |
| 318 | + // 来源于 k8s.io/client-go/tools/cache/store.go |
| 319 | + func (c *cache) Add(obj interface{}) error { |
| 320 | + key, err := c.keyFunc(obj) |
| 321 | + if err != nil { |
| 322 | + return KeyError{obj, err} |
| 323 | + } |
| 324 | + c.cacheStorage.Add(key, obj) |
| 325 | + return nil |
| 326 | + } |
| 327 | + |
| 328 | +cache 类型实现了 `Store` 和 `Indexer` 接口,在很多地方都有应用,如 Informer 和 DeltaFIFO: |
| 329 | + |
| 330 | +1. NewInformer 函数: |
| 331 | + // 来源于 k8s.io/client-go/tools/cache/controller.go |
| 332 | + func NewInformer( |
| 333 | + lw ListerWatcher, |
| 334 | + objType runtime.Object, |
| 335 | + resyncPeriod time.Duration, |
| 336 | + h ResourceEventHandler, |
| 337 | + ) (Store, Controller){ |
| 338 | + clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc) |
| 339 | + fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, clientState) |
| 340 | + cfg := &Config{ |
| 341 | + Queue: fifo, |
| 342 | + ... |
| 343 | + } |
| 344 | + ... |
| 345 | + return clientState, New(cfg) |
| 346 | + } |
| 347 | + |
| 348 | +2. NewIndexerInformer 函数: |
| 349 | + // 来源于 k8s.io/client-go/tools/cache/controller.go |
| 350 | + func NewIndexerInformer( |
| 351 | + lw ListerWatcher, |
| 352 | + objType runtime.Object, |
| 353 | + resyncPeriod time.Duration, |
| 354 | + h ResourceEventHandler, |
| 355 | + indexers Indexers, |
| 356 | + ) (Indexer, Controller) { |
| 357 | + clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers) |
| 358 | + fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, clientState) |
| 359 | + cfg := &Config{ |
| 360 | + Queue: fifo, |
| 361 | + ... |
| 362 | + } |
| 363 | + ... |
| 364 | + return clientState, New(cfg) |
| 365 | + } |
| 366 | +3. NewSharedIndexInformer 函数: |
| 367 | + // k8s.io/client-go/tools/cache/shared_informer.go |
| 368 | + func NewSharedIndexInformer(lw ListerWatcher, objType runtime.Object, defaultEventHandlerResyncPeriod time.Duration, indexers Indexers) SharedIndexInformer { |
| 369 | + realClock := &clock.RealClock{} |
| 370 | + sharedIndexInformer := &sharedIndexInformer{ |
| 371 | + processor: &sharedProcessor{clock: realClock}, |
| 372 | + indexer: NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers), |
| 373 | + listerWatcher: lw, |
| 374 | + objectType: objType, |
| 375 | + resyncCheckPeriod: defaultEventHandlerResyncPeriod, |
| 376 | + defaultEventHandlerResyncPeriod: defaultEventHandlerResyncPeriod, |
| 377 | + cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", objType)), |
| 378 | + clock: realClock, |
| 379 | + } |
| 380 | + return sharedIndexInformer |
| 381 | + } |
| 382 | + |
| 383 | + |
| 384 | +后面文章我们讨论 Informer 使用的 Queue 和 FIFO。 |
0 commit comments