Kubernetes client-go Indexer / ThreadSafeStore 源码分析
 
请阅读原文:原文地址
 

概述

源码版本信息

  • Project: kubernetes
  • Branch: master
  • Last commit id: d25d741c
  • Date: 2021-09-26

Indexer 主要依赖于 ThreadSafeStore 实现,是 client-go 提供的一种缓存机制,通过检索本地缓存可以有效降低 apiserver 的压力,我们在自定义控制器里调用一个 List() 方法带一个 ListOption 本质就是加上条件检索了 Indexer,掌握 Indexer 的实现可以在 Operator 编码时更加心里有底。

Indexer 接口

Indexer 接口主要是在 Store 接口的基础上拓展了对象的检索功能

  • client-go/tools/cache/index.go:35
1
2
3
4
5
6
7
8
9
type Indexer interface {
Store
Index(indexName string, obj interface{}) ([]interface{}, error) // 根据索引名和给定的对象返回符合条件的所有对象
IndexKeys(indexName, indexedValue string) ([]string, error) // 根据索引名和索引值返回符合条件的所有对象的 key
ListIndexFuncValues(indexName string) []string // 列出索引函数计算出来的所有索引值
ByIndex(indexName, indexedValue string) ([]interface{}, error) // 根据索引名和索引值返回符合条件的所有对象
GetIndexers() Indexers // 获取所有的 Indexers,对应 map[string]IndexFunc 类型
AddIndexers(newIndexers Indexers) error // 这个方法要在数据加入存储前调用,添加更多的索引方法,默认只通过 namespace 检索
}

Indexer 的默认实现是 cache

1
2
3
4
type cache struct {
cacheStorage ThreadSafeStore
keyFunc KeyFunc
}

cache 对应两个方法体实现完全一样的 New 函数:

 1
2
3
4
5
6
7
8
9
10
11
12
13
func NewStore(keyFunc KeyFunc) Store {
return &cache{
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
keyFunc: keyFunc,
}
} func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {
return &cache{
cacheStorage: NewThreadSafeStore(indexers, Indices{}),
keyFunc: keyFunc,
}
}

这里涉及到两个类型:

  • KeyFunc
  • ThreadSafeStore

我们先看一下 Indexer 的 Add()、Update() 等方法是怎么实现的:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (c *cache) Add(obj interface{}) error {
key, err := c.keyFunc(obj)
if err != nil {
return KeyError{obj, err}
}
c.cacheStorage.Add(key, obj)
return nil
} func (c *cache) Update(obj interface{}) error {
key, err := c.keyFunc(obj)
if err != nil {
return KeyError{obj, err}
}
c.cacheStorage.Update(key, obj)
return nil
}

可以看到这里的逻辑就是调用 keyFunc() 方法获取 key,然后调用 cacheStorage.Xxx() 方法完成对应增删改查过程。KeyFunc 类型时这样定义的:

1
type KeyFunc func(obj interface{}) (string, error)

也就是给一个对象,返回一个字符串类型的 key。KeyFunc 的一个默认实现如下:

 1
2
3
4
5
6
7
8
9
10
11
12
13
func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
if key, ok := obj.(ExplicitKey); ok {
return string(key), nil
}
meta, err := meta.Accessor(obj)
if err != nil {
return "", fmt.Errorf("object has no meta: %v", err)
}
if len(meta.GetNamespace()) > 0 {
return meta.GetNamespace() + "/" + meta.GetName(), nil
}
return meta.GetName(), nil
}

可以看到一般情况下返回值是 <namespace><name> ,如果 namespace 为空则直接返回 name。类似的还有一个叫做 IndexFunc 的类型,定义如下:

1
type IndexFunc func(obj interface{}) ([]string, error)

这是给一个对象生成 Index 用的,一个通用实现如下,直接返回对象的 namespace 字段作为 Index

1
2
3
4
5
6
7
func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) {
meta, err := meta.Accessor(obj)
if err != nil {
return []string{""}, fmt.Errorf("object has no meta: %v", err)
}
return []string{meta.GetNamespace()}, nil
}

下面我们直接来看 cacheStorage 是如果实现增删改查的。

ThreadSafeStore

ThreadSafeStore 是 Indexer 的核心逻辑所在,Indexer 的多数方法是直接调用内部 cacheStorage 属性的方法实现的,同样先看接口定义:

  • client-go/tools/cache/thread_safe_store.go:41
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type ThreadSafeStore interface {
Add(key string, obj interface{})
Update(key string, obj interface{})
Delete(key string)
Get(key string) (item interface{}, exists bool)
List() []interface{}
ListKeys() []string
Replace(map[string]interface{}, string)
Index(indexName string, obj interface{}) ([]interface{}, error)
IndexKeys(indexName, indexKey string) ([]string, error)
ListIndexFuncValues(name string) []string
ByIndex(indexName, indexKey string) ([]interface{}, error)
GetIndexers() Indexers
AddIndexers(newIndexers Indexers) error
Resync() error // 过期了,没有具体代码逻辑
}

对应实现:

1
2
3
4
5
6
type threadSafeMap struct {
lock sync.RWMutex
items map[string]interface{}
indexers Indexers
indices Indices
}

这里的 Indexers 和 Indices 是:

1
2
3
type Index map[string]sets.String
type Indexers map[string]IndexFunc
type Indices map[string]Index

对照图片理解一下这几个字段的关系:Indexers 里存的是 Index 函数 map,一个典型的实现是字符串 namespace 作为 key,IndexFunc 类型的实现 MetaNamespaceIndexFunc 函数作为 value,也就是我们希望通过 namespace 来检索时,通过 Indexers 可以拿到对应的计算 Index 的函数,接着拿着这个函数,把对象穿进去,就可以计算出这个对象对应的 key,在这里也就是具体的 namespace 值,比如 default、kube-system 这种。然后在 Indices 里存的也是一个 map,key 是上面计算出来的 default 这种 namespace 值,value 是一个 set,而 set 表示的是这个 default namespace 下的一些具体 pod 的 <namespace>/<name> 这类字符串。最后拿着这种 key,就可以在 items 里检索到对应的对象了。

threadSafeMap.Xxx()

比如 Add() 方法代码如下:

1
2
3
4
5
6
7
func (c *threadSafeMap) Add(key string, obj interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
oldObject := c.items[key] // c.items 是 map[string]interface{} 类型
c.items[key] = obj // 在 items map 里添加这个对象
c.updateIndices(oldObject, obj, key) // 下面分析
}

可以看到更复杂的逻辑在 updateIndices 方法里,我们继续来看:

  • client-go/tools/cache/thread_safe_store.go:256
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) {
// 添加场景这里是 nil,如果是更新,就需要删除旧对象的索引了
if oldObj != nil {
c.deleteFromIndices(oldObj, key) // 删除操作后面具体看
}
for name, indexFunc := range c.indexers { // 从 Indexers 里拿到索引函数,比如 "namespace":MetaNamespaceIndexFunc
indexValues, err := indexFunc(newObj) // 通过 MetaNamespaceIndexFunc 计算得到 namespace,比如 "default"
if err != nil {
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
}
index := c.indices[name] // 拿到一个 Index,对应类型 map[string]sets.String
if index == nil {
index = Index{}
c.indices[name] = index // 如果 map 不存在则初始化一个
} for _, indexValue := range indexValues { // "default"
set := index[indexValue] // 检索 "default" 下的 set,对应一个集合,多个 pod 信息
if set == nil {
set = sets.String{}
index[indexValue] = set // 如果为空则初始化一个
}
set.Insert(key) // key 也就是类似 "default/pod_1" 这样的字符串,保存到 set 里,也就完成了 key + obj 的 Add 过程
}
}
}

上面还提到了一个 deleteFromIndices 方法,前半段和上面逻辑上类似的,最后拿到 set 后不同于上面的 Insert 过程,这里调用了一个 Delete。

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (c *threadSafeMap) deleteFromIndices(obj interface{}, key string) {
for name, indexFunc := range c.indexers {
indexValues, err := indexFunc(obj)
if err != nil {
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
} index := c.indices[name]
if index == nil {
continue
}
for _, indexValue := range indexValues {
set := index[indexValue]
if set != nil {
set.Delete(key) // set 中删除这个 key
if len(set) == 0 {
delete(index, indexValue)
}
}
}
}
}

Index() 等实现

最后看几个具体方法等实现

Index() 方法

来看一下 Index() 方法的实现,Index() 方法的作用是给定一个 obj 和 indexName,比如 pod1和 “namespace”,然后返回 pod1 所在 namespace 下的所有 pod。

  • client-go/tools/cache/thread_safe_store.go:141
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) {
c.lock.RLock()
defer c.lock.RUnlock() indexFunc := c.indexers[indexName] // 提取索引函数,比如通过 "namespace" 提取到 MetaNamespaceIndexFunc
if indexFunc == nil {
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
} indexedValues, err := indexFunc(obj) // 对象丢进去拿到索引值,比如 "default"
if err != nil {
return nil, err
}
index := c.indices[indexName] // indexName 例如 "namespace",这里可以查到 Index var storeKeySet sets.String
if len(indexedValues) == 1 {
// 多数情况对应索引值为1到场景,比如用 namespace 时,值就是唯一的
storeKeySet = index[indexedValues[0]]
} else {
// 对应不为1场景
storeKeySet = sets.String{}
for _, indexedValue := range indexedValues {
for key := range index[indexedValue] {
storeKeySet.Insert(key)
}
}
} list := make([]interface{}, 0, storeKeySet.Len())
// storeKey 也就是 "default/pod_1" 这种字符串,通过其就可以到 items map 里提取需要的 obj 了
for storeKey := range storeKeySet {
list = append(list, c.items[storeKey])
}
return list, nil
}

ByIndex() 方法

相比 Index(),这个函数要简单的多,直接传递 indexedValue,也就不需要通过 obj 去计算 key 了,例如 indexName == namespace & indexValue == default 就是直接检索 default 下的资源对象。

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (c *threadSafeMap) ByIndex(indexName, indexedValue string) ([]interface{}, error) {
c.lock.RLock()
defer c.lock.RUnlock() indexFunc := c.indexers[indexName]
if indexFunc == nil {
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
} index := c.indices[indexName] set := index[indexedValue]
list := make([]interface{}, 0, set.Len())
for key := range set {
list = append(list, c.items[key])
} return list, nil
}

IndexKeys() 方法

和上面返回 obj 列表不同,这里只返回 key 列表,就是 []string{“default/pod_1”} 这种数据

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (c *threadSafeMap) IndexKeys(indexName, indexedValue string) ([]string, error) {
c.lock.RLock()
defer c.lock.RUnlock() indexFunc := c.indexers[indexName]
if indexFunc == nil {
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
} index := c.indices[indexName] set := index[indexedValue]
return set.List(), nil
}

Replace() 方法

Replace() 的实现简单粗暴,给一个新 items map,直接替换到 threadSafeMap.items 中,然后重建索引。

 1
2
3
4
5
6
7
8
9
10
11
func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) {
c.lock.Lock()
defer c.lock.Unlock()
c.items = items // rebuild any index
c.indices = Indices{}
for key, item := range c.items {
c.updateIndices(nil, item, key)
}
}

(转载请保留本文原始链接)

https://www.danielhu.cn/post/k8s/client-go-indexer/

 

Kubernetes client-go Indexer / ThreadSafeStore 源码分析的更多相关文章

  1. kubernetes垃圾回收器GarbageCollector Controller源码分析(二)

    kubernetes版本:1.13.2 接上一节:kubernetes垃圾回收器GarbageCollector Controller源码分析(一) 主要步骤 GarbageCollector Con ...

  2. eureka client服务续约源码分析

    必备知识: 1.定时任务 ScheduledExecutorService public class demo { public static void main(String[] args){ Sc ...

  3. Kubernetes client-go DeltaFIFO 源码分析

    概述Queue 接口DeltaFIFO元素增删改 - queueActionLocked()Pop()Replace() 概述 源码版本信息 Project: kubernetes Branch: m ...

  4. Kubernetes client-go Informer 源码分析

    概述ControllerController 的初始化Controller 的启动processLoopHandleDeltas()SharedIndexInformersharedIndexerIn ...

  5. Kubernetes Deployment 源码分析(二)

    概述startDeploymentController 入口逻辑DeploymentController 对象DeploymentController 类型定义DeploymentController ...

  6. client-go客户端自定义开发Kubernetes及源码分析

    介绍 client-go 是一种能够与 Kubernetes 集群通信的客户端,通过它可以对 Kubernetes 集群中各资源类型进行 CRUD 操作,它有三大 client 类,分别为:Clien ...

  7. k8s client-go源码分析 informer源码分析(6)-Indexer源码分析

    client-go之Indexer源码分析 1.Indexer概述 Indexer中有informer维护的指定资源对象的相对于etcd数据的一份本地内存缓存,可通过该缓存获取资源对象,以减少对api ...

  8. 【原】Spark中Client源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...

  9. Docker源码分析(二):Docker Client创建与命令执行

    1. 前言 如今,Docker作为业界领先的轻量级虚拟化容器管理引擎,给全球开发者提供了一种新颖.便捷的软件集成测试与部署之道.在团队开发软件时,Docker可以提供可复用的运行环境.灵活的资源配置. ...

随机推荐

  1. ProjectEuler 003题

    1 //题目:The prime factors of 13195 are 5, 7, 13 and 29. 2 //What is the largest prime factor of the n ...

  2. Maven无法导入插件,pom文件报错

    最近在使用IDEA导入开源项目bootshiro,更新依赖的时候,发现有些插件无法导入,以致于pom文件一直报找不到该插件的错误 一开始就网上各种百度,无论怎么更换阿里云的镜像都导不进,最后想着试试自 ...

  3. C#·好文分享

    时间:2018-11-14 记录:byzqy 好文收藏,集中分享! 标题:C#接口<通俗解释> 地址:https://www.cnblogs.com/hamburger/p/4681681 ...

  4. linux centos7 移动文件到指定目录

    2021-08-26 在 centos7 环境下怎么移动一个文件到其他的目录下呢? 使用命令  mv 文件名 指定目录  即可完成该操作. 那么怎么将一个文件夹下的内容移动到另一个文件夹下呢?比如有时 ...

  5. D3之svg transform 与 css3 transform 区别与联系

    D3就不用多介绍了,在数据可视化界属于大佬级别的js库.在这里主要想记录一下在写程序期间遇到的一个问题. 如下图所示,想完成主视图在小地图上的映射,小地图的白色矩形框用来代表当前主视图可见区域,主视图 ...

  6. C#使用异步需要注意的几个问题

    C#异步使用需要注意的几个问题1.异步方法如果只是对别的方法的简单的转发调用,没哟复杂的逻辑(比如等待A的结果,再调用B,等待A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字. ...

  7. Delphi使用AcroPDF ActiveX显示PDF文件

    效果展示 调用方式 放入窗体即可使用,不想安装太多组件,可使用纯代码方式调用 interface ..... var AcroPDF: TAcroPDF; .... implementation .. ...

  8. Vue Abp vNext用户登录(Cookie)

    因为Abp vNext没找到Vue的模板,网上也没找到相关vNext的例子,只能自己试着写写,asp.net core abp vue都是刚学不久,所以很粗糙也可能有错误的地方,如果您看到请指正,谢谢 ...

  9. go相关

    mac 上build go  如果想要在centos上面执行 必须使用下面的方式 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o hello ...

  10. 使用zipKin构建NetCore分布式链路跟踪

    本文主要讲解使用ZipKin构建NetCore分布式链路跟踪 场景 因为最近公司业务量增加,而项目也需要增大部署数量,K8S中Pod基本都扩容了一倍,新增了若干物理机,部分物理机网络通信存在问题,导致 ...