client-go的Indexer三部曲之一:基本功能

news2024/10/6 18:25:22

关于《client-go的Indexer三部曲》系列

  • 该系列是《client-go实战系列》的子系列文章,共三篇内容,分别从功能、性能、源码三个角度对client-go内部的Indexer组件进行说明,目标是与大家一同学习Indexer,并掌握如何在开发中通过Indexer实现灵活、高性能的查询检索操作
  • 再来回顾一下client-go的基本架构图,如下图所示,这次重点关注的是中间那根横向的虚线,可见在自定义的业务开发过程中,当我们需要得到某个资源对象的时候,是可以通过Indexer直接从本地缓存中获取的(Thread safe store)
    在这里插入图片描述
  • 从用户角度来看,Indexer的作用就是可以帮助用户从本地获取资源对象,由于client-go的List & Watch机制,资源在kubernetes的所有变化都会及时同步到本地缓存中,这也就保证了本地缓存的数据是实时更新的

为什么要用Indexer获取资源?

  • 看到这里您可能会有疑问:从用户视角来看,client-go已经提供了API,可以远程访问api-server获取完整对象,那不就够用了吗?这里用Indexer获取本地对象有什么意义呢?

  • 理论上讲,从本地缓存获取数据属于进程内操作,如下图所示
    在这里插入图片描述

  • 如果使用client-go提供的远程接口,就涉及到网络请求,理论上耗时更长
    在这里插入图片描述

  • 从api-server视角来看还有另一层含义:走本地缓存后,来api-server查询的次数就少了,这也就降低了api-server的负载

  • 接下来就通过编码实战的方式,和大家一起了解Indexer的基本用法,另外为了印证本地缓存的性能优势,还会写一段远程访问api-server查询对象的代码,最后用性能工具同时对比两者,看看性能差距是否存在

  • 经过上述操作,相信咱们可以快速了解Indexer的实际用法,还能通过数据对其有更具体的认识,有了这些基础,在后面深入学习Indexer源码的时候,似乎可以轻松很多,每当您看到一段源码,对其设计原因和实际作用都有更多的认识,嗯,这也是欣宸一直推崇的学习方法:实战,不停的实战,拒绝凭空读代码

准备工作

  • 除了保证kubernetes环境正常,还要部署一些资源,以便稍后演示Indexer的功能
  • 先在kubernetes部署三种deployment,分别是:nginx、tomcat、mysql,完整的部署脚本可以在这里下载:https://github.com/zq2599/blog_demos/tree/master/tutorials/client-go-indexer-tutorials/scripts/kubernetes ,共有下图这些文件
    在这里插入图片描述
  • 由于涉及到本地存储,需要您根据自己电脑的情况修改deploy.sh的内容,如下图红色箭头所示,请修改成您自己电脑的有效路径
    在这里插入图片描述
  • 执行deploy.sh即可完成部署,如果要清理掉所有部署的痕迹,执行deploy.sh即可
  • 部署成功后,在indexer-tutorials这个namespace下新增了五个pod,如下所示
kubectl get pods -n indexer-tutorials -o wide
NAME                                          READY   STATUS    RESTARTS   AGE    IP             NODE   NOMINATED NODE   READINESS GATES
mysql-556b999fd8-22hqh                        1/1     Running   0          8m4s   100.91.64.49   hedy   <none>           <none>
nginx-deployment-696cc4bc86-2rqcg             1/1     Running   0          8m1s   100.91.64.50   hedy   <none>           <none>
nginx-deployment-696cc4bc86-bkplx             1/1     Running   0          8m2s   100.91.64.46   hedy   <none>           <none>
nginx-deployment-696cc4bc86-m7wwh             1/1     Running   0          8m1s   100.91.64.47   hedy   <none>           <none>
tomcat-deployment-nautilus-7fcb47fcc4-vvdq6   1/1     Running   0          8m3s   100.91.64.48   hedy   <none>           <none>

准备一批业务需求

  • 本篇的目标是学会Indexer最基本的使用方式,因此先准备几个业务需求,然后用Indexer来实现这些需求,这样也算是有了具体的目标,可以更容易理解和掌握技术
  • 回顾刚才的部署脚本,如下图,nginx、tomcat、mysql都有两个自定义label:languagebusiness-service-type
    在这里插入图片描述
  • 下面的表格说明了nginx、tomcat、mysql这些pod的两个label的具体值
pod语言类型(language)服务类型(business-service-type)
nginxcweb
tomcatcweb
mysqljavastorage
  • 根据上述表格可见,nginx、tomcat、mysql这些pod,一共有两种分类方式:
  1. 按照语言类型分类,nginx和mysql都是c语言类,tomcat是java类
  2. 按照服务类型分类,nginx和tomcat都是web类,mysql是存储类
  • 请记住上述分类方式,接下来可以列出业务需求了
  1. 根据指定语言查询对应的pod的key,例如输入java,返回tomcat的pod的key
  2. 根据指定的pod的key查询pod对象,例如输入indexer-tutorials/nginx-deployment-87945df85-vzjgv,返回nginx的pod对象
  3. 查询指定语言的所有对象,例如输入java,返回tomcat的pod
  4. 根据某个对象的key,获取同语言类型的所有对象,例如输入indexer-tutorials/nginx-deployment-87945df85-vzjgv,返回的就是nginx和mysql的所有pod,因为它们的语言类型都是c
  5. 返回所有语言类型,这里应该是c和java
  6. 返回所有分类方式,这里应该是按服务类型和按语言类型两种
  • 接下来开始编码啦,用Indexer来实现上述六个需求

接口一览

  • 编码前先初步了解Indexer,直接看源码吧,毕竟稍后就要用到,结合刚刚准备好的kubernetes部署以及业务需求,这里对Indexer的接口做了详细的说明,请注意方法的注释
type Indexer interface {
	// 存储相关的,不在本章讨论
	Store
	
	// indexName表示分类方式,obj表示用来查询的对象,
	// 例如indexName等于BY_LANGUAGE,obj等于nginx的pod对象,
	// 那么Index方法就会根据BY_LANGUAGE去获取pod对象的语言类型,即c语言,再返回所有c语言类型的对象
	// 简而言之就是:查找和obj同一个语言类型的所有对象
	Index(indexName string, obj interface{}) ([]interface{}, error)

	// indexName表示分类方式,indexedValue表示分类的值,
	// 例如indexName等于BY_LANGUAGE,indexedValue等于c,
	// 那么IndexKeys方法就会返回所有语言类型等于c的对象的key
	IndexKeys(indexName, indexedValue string) ([]string, error)
	
	// indexName表示分类方式,
	// 例如indexName等于BY_LANGUAGE,
	// ListIndexFuncValues返回的就是java和c
	ListIndexFuncValues(indexName string) []string
	
	// indexName表示分类方式,indexedValue表示分类的值,
	// 例如indexName等于BY_LANGUAGE,indexedValue等于c,
	// 那么ByIndex方法就会返回所有语言类型等于c的对象
	ByIndex(indexName, indexedValue string) ([]interface{}, error)
	
	// Indexers是个map,key是分类方式,
	// 本文中key有两个,分别是BY_LANGUAGE和BY_SERVICE,
	// value则是个方法,
	// key等于BY_LANGUAGE的时候,该方法的入参是个对象pod,返回值是这个pod的语言,
	// key等于BY_SERVICE的时候,该方法的入参是个对象pod,返回值是这个pod的服务类型,
	GetIndexers() Indexers
	// 添加Indexers
	AddIndexers(newIndexers Indexers) error
}

源码下载

  • 接下来要进入的是编码环节,如果您不想编写代码,也可以从GitHub上直接下载,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称链接备注
项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议
git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在tutorials/client-go-indexer-tutorials文件夹下,如下图红框所示:
    在这里插入图片描述

项目简介

  • 接下来要开发的项目,使用常见的gin作为web框架,提供web接口来实现上述六个需求
  • 为了省事儿,这边会将编译好的二进制文件直接部署在kubernetes机器上,这样运行起来就能直接用上配置文件/root/.kube/config
  • 为了使用Indexer,需要做一些初始化操作,这里提前梳理出来,稍后只要对着这个流程图实现编码即可
    在这里插入图片描述

编码,搭建框架

  • 新建名为client-go-indexer-tutorials的文件夹,构建一个新的module
go mod init client-go-indexer-tutorials
  • 下载gin
go get -u github.com/gin-gonic/gin
  • main.go的内容很简单:执行kubernetes初始化相关的方法,再设定好六个web接口的handler
package main

import (
	"client-go-indexer-tutorials/basic"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	// kubernetes相关的初始化操作
	basic.DoInit()

	// 用于提供基本功能的路由组
	basicGroup := r.Group("/basic")

	// a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
	basicGroup.GET("get_obj_keys_by_language_name", basic.GetObjKeysByLanguageName)

	// b. 返回对象的key,返回对应的对象(演示Store.GetByKey方法)
	basicGroup.GET("get_obj_by_obj_key", basic.GetObjByObjKey)

	// c. 查询指定语言的所有对象(演示4. ByIndex方法)
	basicGroup.GET("get_obj_by_language_name", basic.GetObjByLanguageName)

	// d. 根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)
	basicGroup.GET("get_all_obj_by_one_name", basic.GetAllObjByOneName)

	// e. 返回所有语言类型(演示3. ListIndexFuncValues方法)
	basicGroup.GET("get_all_languange", basic.GetAllLanguange)

	// f. 返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)
	basicGroup.GET("get_all_class_type", basic.GetAllClassType)

	r.Run(":18080")
}

编码,业务功能

  • 完整的业务代码如下,已经添加了详细注释,另有几处要注意的地方,稍后会说明
package basic

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"path/filepath"
	"sync"

	"github.com/gin-gonic/gin"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

const (
	NAMESPACE      = "indexer-tutorials"
	PARAM_LANGUAGE = "language"
	PARAM_OBJ_KEY  = "obj_key"

	LANGUAGE_C = "c"

	INDEXER_LANGUAGE              = "indexer_language"
	INDEXER_BUSINESS_SERVICE_TYPE = "indexer_business_service_type"

	LABEL_LANGUAGE              = "language"
	LABEL_BUSINESS_SERVICE_TYPE = "business-service-type"
)

var ClientSet *kubernetes.Clientset
var once sync.Once
var INDEXER cache.Indexer

// DoInit Indexer相关的初始化操作,这里确保只执行一次
func DoInit() {
	once.Do(initIndexer)
}

// initIndexer 这里是真正的初始化逻辑
func initIndexer() {
	log.Println("开始初始化Indexer")

	var kubeconfig *string

	// 试图取到当前账号的家目录
	if home := homedir.HomeDir(); home != "" {
		// 如果能取到,就把家目录下的.kube/config作为默认配置文件
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}

	// 加载配置文件
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// 用clientset类来执行后续的查询操作
	ClientSet, err = kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	log.Println("kubernetes配置文件加载成功")

	// 确定从apiserver订阅的类型
	podListWatcher := cache.NewListWatchFromClient(ClientSet.CoreV1().RESTClient(), "pods", NAMESPACE, fields.Everything())

	// Indexers对象的类型是map,key是自定义字符串,value是个function,用于根据业务逻辑返回一个对象的字符串
	indexers := cache.Indexers{
		INDEXER_LANGUAGE: func(obj interface{}) ([]string, error) {
			var object metav1.Object
			object, err = meta.Accessor(obj)
			if err != nil {
				return []string{}, nil
			}

			labelValue := object.GetLabels()[LABEL_LANGUAGE]
			if labelValue == "" {
				return []string{}, nil
			}
			return []string{labelValue}, nil
		},
		INDEXER_BUSINESS_SERVICE_TYPE: func(obj interface{}) ([]string, error) {
			var object metav1.Object
			object, err = meta.Accessor(obj)
			if err != nil {
				return []string{}, nil
			}

			labelValue := object.GetLabels()[LABEL_BUSINESS_SERVICE_TYPE]
			if labelValue == "" {
				return []string{}, nil
			}
			return []string{labelValue}, nil
		},
	}

	var informer cache.Controller

	INDEXER, informer = cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{}, indexers)

	log.Println("Indexer初始化成功")

	stopCh := make(chan struct{})

	// informer的Run方法执行后,就开始接受apiserver推送的资源变更事件,并更新本地存储
	go informer.Run(stopCh)

	// 等待本地存储和apiserver完成同步
	if !cache.WaitForCacheSync(stopCh, informer.HasSynced) {
		err = errors.New("timed out waiting for caches to sync")
		runtime.HandleError(err)
		return
	}

	log.Println("pod加载完成")

}

// language 辅助方法,从请求参数中获取语言类型,默认返回c
func language(c *gin.Context) string {
	return c.DefaultQuery(PARAM_LANGUAGE, LANGUAGE_C)
}

// objKey 辅助方法,从请求参数中获取对象key
func objKey(c *gin.Context) string {
	return c.DefaultQuery(PARAM_OBJ_KEY, "")
}

// getObjKeysByLanguageName a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
func GetObjKeysByLanguageName(c *gin.Context) {
	language := language(c)

	v, err := INDEXER.IndexKeys(INDEXER_LANGUAGE, language)

	if err != nil {
		c.String(500, fmt.Sprintf("a. get pod failed, %v", err))
	} else if nil == v || len(v) < 1 {
		c.String(500, fmt.Sprintf("a. get empty pod, %v", err))
	} else {
		m := make(map[string][]string)
		m["language"] = v
		c.JSON(200, m)
	}
}

// GetObjByObjKey b. 根据对象的key返回(演示Store.Get方法)
func GetObjByObjKey(c *gin.Context) {
	rawObj, exists, err := INDEXER.GetByKey(objKey(c))

	if err != nil {
		c.String(500, fmt.Sprintf("b. get pod failed, %v", err))
	} else if !exists {
		c.String(500, fmt.Sprintf("b. get empty pod, %v", err))
	} else {
		if v, ok := rawObj.(*v1.Pod); ok {
			c.JSON(200, v)
		} else {
			c.String(500, "b. convert interface to pod failed")
		}
	}
}

// getObjByLanguageName c. 查询指定语言的所有对象(演示4. ByIndex方法)
func GetObjByLanguageName(c *gin.Context) {
	v, err := INDEXER.ByIndex(INDEXER_LANGUAGE, language(c))

	if err != nil {
		c.String(500, fmt.Sprintf("c. get pod failed, %v", err))
	} else if v == nil {
		c.String(500, fmt.Sprintf("c. get empty pod, %v", err))
	} else {
		m := make(map[string][]interface{})
		m["language"] = v
		c.JSON(200, m)
	}
}

// getAllObjByOneName d. 根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)
func GetAllObjByOneName(c *gin.Context) {
	// 注意,Index方法的第二个入参是对象,所以这里要先根据对象key查询到对象,然后再调用Index方法
	rawObj, exists, err := INDEXER.GetByKey(objKey(c))

	if err != nil {
		c.String(500, fmt.Sprintf("d1. get pod failed, %v", err))
	} else if !exists {
		c.String(500, fmt.Sprintf("d1. get empty pod, %v", err))
	} else {
		// 先得到pod对象,再根据pod对象查询同类型的所有对象
		if podObj, ok := rawObj.(*v1.Pod); ok {
			rawArray, err := INDEXER.Index(INDEXER_LANGUAGE, podObj)

			if err != nil {
				c.String(500, fmt.Sprintf("d2. get pod failed, %v", err))
			} else if len(rawArray) < 1 {
				c.String(500, fmt.Sprintf("d2. get empty pod, %v", err))
			} else {
				m := make(map[string][]interface{})
				m["language"] = rawArray
				c.JSON(200, m)
			}
		} else {
			c.String(500, "d1. convert interface to pod failed")
		}
	}
}

// getAllClassType e. 返回所有语言类型(演示3. ListIndexFuncValues方法)
func GetAllLanguange(c *gin.Context) {
	languages := INDEXER.ListIndexFuncValues(INDEXER_LANGUAGE)

	m := make(map[string][]string)
	m["language"] = languages

	c.JSON(200, m)
}

// getAllClassType f. 返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)
func GetAllClassType(c *gin.Context) {
	indexers := INDEXER.GetIndexers()
	// indexers是个map,其value是cache.IndexFunc类型,无法被序列化,所以这里只返回key
	names := make([]string, 0)
	for key, _ := range indexers {
		names = append(names, key)
	}
	c.JSON(200, names)
}
  • 上述代码有以下几处需要注意
  1. 注意区分Indexers和Indexer,两者不是同一个东西,Indexers是个map,Indexer提供了各种查询API,Indexer是本篇我们要重点关注的内容
  2. 对应Indexers,key是分类方式的名称,这个可以自己起名字,value是方法,用于自定义如何设定该对象在此分类下的的结果,举个例子,如果按照语言分类,那么key等于"indexer_language",value是个方法,该方法用于返回一个pod的label值,该label就是"language"
  3. Indexers中的func,让沉迷于Java的我想起了一段Java代码,就是自定义排序,也是自己写好具体的排序逻辑,至于如何使用这个逻辑就无需关心了,只要调用Arrays.sort就好,如下所示
public class CustomSort {
    public static void main(String[] args) {
        String[] arr = {"banana", "apple", "orange", "pear"};
        
        // 使用自定义排序方法进行排序
        Arrays.sort(arr, new CustomComparator());
        
        // 输出排序后的数组
        for (String str : arr) {
            System.out.print(str + " ");
        }
    }
}

class CustomComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        // 按照字符串长度进行排序
        return Integer.compare(s1.length(), s2.length());
    }
}

编码,运行脚本

  • 如果您使用的是vscode,可以把launch.json配置如下,以便使用运行和调试功能
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceFolder}"
        }
    ]
}

编码,测试脚本

  • 写到这里,其实可以运行程序,然后用postman或者直接用浏览器来访问接口验证效果了,不过为了将操作也能归档,以便今后也能随时再次操作,我这里使用了vscode的REST Client插件,把所有web请求放在脚本中,借助插件来完成请求、获取响应,您若对REST Client没有兴趣可以跳过这段
  • 新建hello.http文件,内容如下
### 变量,这是一个pod的对象key,和pod是一对一的关系
@obj_key=indexer-tutorials/nginx-deployment-87945df85-vzjgv

### 测试用例a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
GET http://192.168.50.76:18080/basic/get_obj_keys_by_language_name?language=c

### 测试用例b. 返回对象的key,返回对应的对象(演示Store.GetByKey方法)
GET http://192.168.50.76:18080/basic/get_obj_by_obj_key?obj_key={{obj_key}}

### 测试用例c. 查询指定语言的所有对象(演示4. ByIndex方法)
GET http://192.168.50.76:18080/basic/get_obj_by_language_name?language=c

### 测试用例d. 根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)
GET http://192.168.50.76:18080/basic/get_all_obj_by_one_name?obj_key={{obj_key}}

### 测试用例e. 返回所有语言类型(演示3. ListIndexFuncValues方法)
GET http://192.168.50.76:18080/basic/get_all_languange

### 测试用例f. 返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)
GET http://192.168.50.76:18080/basic/get_all_class_type

验证,查询指定语言的所有对象的key(演示2. IndexKeys方法)

  • 运行程序,可见如果和kubernetes同机部署,首次加载全量数据也不会消耗太长时间
    在这里插入图片描述
  • 测试,第一个请求如下
http://192.168.50.76:18080/basic/get_obj_keys_by_language_name?language=c
  • 如果是REST Client,点击下图黄色箭头位置即可
    在这里插入图片描述
  • 收到响应如下,可见编程语言是c的pod一共有四个,一个mysql,三个nginx的,它们的key都在JSON数组中返回,符合预期,
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 22:56:37 GMT
Content-Length: 219
Connection: close

{
  "language": [
    "indexer-tutorials/mysql-556b999fd8-22hqh",
    "indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg",
    "indexer-tutorials/nginx-deployment-696cc4bc86-bkplx",
    "indexer-tutorials/nginx-deployment-696cc4bc86-m7wwh"
  ]
}
  • 现在拿到了对象的key,可见是namespace和pod name拼接而成,接下来试试用key得到对象,这是Store的基本功能

验证,查询指定语言的所有对象的key(演示Store.GetByKey方法)

  • 如果REST Client,可以用变量来保存对象key,这样便于维护,如下图
    在这里插入图片描述
  • 根据对象key查询资源对象的path
http://192.168.50.76:18080/basic/get_obj_by_obj_key?obj_key=indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg
  • 响应如下(篇幅所限只显示部分),可见是一个完整的pod信息
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:19:50 GMT
Connection: close
Transfer-Encoding: chunked

{
  "metadata": {
    "name": "nginx-deployment-696cc4bc86-2rqcg",
    "generateName": "nginx-deployment-696cc4bc86-",
    "namespace": "indexer-tutorials",
    "uid": "f079b9e3-2b0f-4090-8d0f-5bab4fa2eeda",
    "resourceVersion": "1341749",
    "creationTimestamp": "2023-06-12T23:04:58Z",
    "labels": {
      "app": "nginx-app",
      "business-service-type": "web",
      "language": "c",
      "pod-template-hash": "696cc4bc86",
      "type": "front-end"
    },
    ...

验证, 查询指定语言的所有对象(演示4. ByIndex方法)

  • 刚才咱们试过了get_obj_keys_by_language_name,可以根据语言查找对象的key,现在来试试根据语言查对象
http://192.168.50.76:18080/basic/get_obj_by_language_name?language=c
  • 响应(篇幅所限只显示部分)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:25:00 GMT
Connection: close
Transfer-Encoding: chunked

{
  "language": [
    {
      "metadata": {
        "name": "mysql-556b999fd8-22hqh",
        "generateName": "mysql-556b999fd8-",
        "namespace": "indexer-tutorials",
        "uid": "ac7ca6a2-f463-450d-848a-f3a2ea6a02df",
        "resourceVersion": "1341711",
        "creationTimestamp": "2023-06-12T23:04:55Z",
        "labels": {
          "app": "mysql",
          "business-service-type": "storage",
          "language": "c",
          "pod-template-hash": "556b999fd8"
        },

验证,根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)

  • 用nginx对象的key,能查到同类型语言的所有对象
http://192.168.50.76:18080/basic/get_all_obj_by_one_name?obj_key=indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg
  • 响应
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:30:11 GMT
Connection: close
Transfer-Encoding: chunked

{
  "language": [
    {
      "metadata": {
        "name": "nginx-deployment-696cc4bc86-bkplx",
        "generateName": "nginx-deployment-696cc4bc86-",
        "namespace": "indexer-tutorials",
        "uid": "eba92053-bc35-4ef5-835a-1c2f054891d5",
        "resourceVersion": "1341721",
        "creationTimestamp": "2023-06-12T23:04:57Z",
        "labels": {
          "app": "nginx-app",
          "business-service-type": "web",
          "language": "c",
          "pod-template-hash": "696cc4bc86",
          "type": "front-end"
        },
	...

验证,返回所有语言类型(演示3. ListIndexFuncValues方法)

  • 可以指定分类方式后,看到该方式分类后共有哪些值
http://192.168.50.76:18080/basic/get_all_languange
  • 可见共有两种
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:32:37 GMT
Content-Length: 25
Connection: close

{
  "language": [
    "java",
    "c"
  ]
}

验证,返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)

  • 由于具体的分类方式是函数,无法做序列化,因此返回的是分类方式对应的name
http://192.168.50.76:18080/basic/get_all_class_type
  • 响应
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:59:56 GMT
Content-Length: 52
Connection: close

[
  "indexer_language",
  "indexer_business_service_type"
]
  • 至此,Indexer的基本功能已经体验完成,对于如何使用Indexer相信您已经掌握了,接下来咱们会进入性能篇,来看看这个理论上的高性能在实际环境中有着怎样的表现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/648560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EBU5476 Microprocessor System Design 知识点总结_6 Serial Communication

Serial Communication 串口通信&#xff0c;一种发送消息的通信方式。 串&#xff0c;指的是发数据的方式&#xff1a;一位一位串行发&#xff0c;并行是可能有多路通道&#xff0c;每路同时发一个数据&#xff0c;多路同时到达。 串口通信有单工 Simplex&#xff0c;半双工…

基于SpringBoot+Vue的乐校园二手书交易管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

35. 应用监控【监控端点健康信息】

1、展示健康信息详情 开发者可以通过查看健康信息来获取应用的运行数据&#xff0c;进而提早发现应用问题&#xff0c;提早解决&#xff0c; 免造成损失。默认情况下开发者只能获取 status 信息&#xff08;见图 1 &#xff09;&#xff0c;这是因为 detail 信息默认不显示&…

【Java常见面试题】Spring篇

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线常见面试题 目录 1、简单介绍Spring 2、说说你对IOC的理解 3、说说你对AOP的理解 4、说说Bean的生命周期 5、说说循环依赖和三级缓存 6、说说Bean的几种…

DataV图表-排名轮播表自定义

DataV图表-排名轮播表自定义数据大屏可视化 场景&#xff1a;需要计算根据分数不同柱子的颜色不同 低于60分变成为橙色柱子 一开始使用的是 dv-scroll-ranking-board 这个不可以自定义颜色和属性 我们可以更改 dv-scroll-board 样式来实现 排名轮播表 安装 data-view npm ins…

如何使用Leangoo领歌管理敏捷缺陷

缺陷管理通常关注如下几个方面&#xff1a; 1. 缺陷的处理速度 2. 缺陷处理的状态 3. 缺陷的分布 4. 缺陷产生的原因 使用​​​​​​​Leangoo领歌敏捷工具​​​​​​​我们可以对缺陷进行可视化的管理&#xff0c;方便我们对缺陷的处理进展、负责人、当前状态、分布情…

『 MySQL篇 』:MySQL 锁机制介绍

目录 一. 概述 二. 全局锁 三 . 表级锁 三. 行级锁 一. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据…

三菱FX5U通讯、定位、伺服32讲

三菱FX5U系列&#xff08;现在已经升级改成为MELSEC iQ-F系列&#xff09;PLC的CPU模块中内置了能够支持各种控制的优异功能&#xff0c;全系标配Ethernet端口、RS-485端口、SD存储卡槽。Ethernet端口可支持CC-Link IE现场网络Basic&#xff0c;因此能连接多种多样的设备。 第一…

eChart折线图动态特效和隔几秒高亮特效

示例&#xff1a; 说明&#xff1a; 因为现在公司经常要做大屏可视化特效&#xff0c;没办法&#xff0c;只能让图尽量动起来&#xff08;之前开会挨叼了&#xff0c;说俺们深圳做的&#xff0c;不能比西安那些人做的差。。。&#xff09; 主要代码&#xff1a; 折线图的滚呀滚…

基于Hexo和Butterfly创建个人技术博客,(7) 配置butterfly主题搭建博客网站主体UI框架

Butterfly官方网站&#xff0c;请 点击进入。 本文面向使用 butterfly theme 的用户, 主题安装方法可查看基于Hexo和Butterfly创建个人技术博客&#xff0c;(1) 初始化博客站点 这章内容。 一、概述 1、什么是theme? Hexo可以认为是一个基础框架&#xff0c;主要提供渲染和插…

平行云——开启通往元宇宙的通道

元宇宙是平行于真实世界的虚拟世界&#xff0c;是新一代互联网。具有真三维、可交互、可沉浸特性的XR&#xff0c;是构建元宇宙的终极数字媒体形态。如何打破XR终端设备与XR内容之间的紧耦合&#xff0c;实现任意平台、任意终端的线上访问&#xff0c;Cloud XR是其必由之路&…

Diffie-Hellman Key Agreement Protocol 资源管理错误漏洞(CVE-2002-20001)

详细描述: Diffie-Hellman Key Agreement Protocol是一种密钥协商协议。它最初在 Diffie 和 Hellman 关于公钥密码学的开创性论文中有所描述。该密钥协商协议允许 Alice 和 Bob 交换公钥值&#xff0c;并根据这些值和他们自己对应的私钥的知识&#xff0c;安全地计算共享密钥K…

C语言柔型数组

何为柔性数组 所谓柔性数组&#xff0c;是C语言中的一个概念&#xff0c;也叫零长数组。顾名思义&#xff0c;这个数组的长度是不固定的&#xff0c;当没有值时&#xff0c;它的sizeof长度为0。 我们一般这样定义一个柔性数组&#xff1a; struct buffer_t {int len;char buf…

FasterTransformer 002: cuda调试env

VSCODE ENV cmake NVIDIA Nsight Systems 当我们装好了CUDA的时候&#xff0c;其实在图形界面下已经装好了一个叫“nsight”的编译器&#xff0c;我们可以直接用终端打开这个编译器&#xff0c;然后写好程序直接编译然后debug就可以了。WINDOWS NVIDIA Nsight Systems 入门及…

【ARM AMBA APB 入门 1 -- APB总线介绍】

文章目录 APB 总线历史1.1 APB 总线介绍1.1.1 APB 使用场景1.1.2 APB 信号列表1.1.3 APB 状态机 1.2 APB 传输时序1.2.1 APB 写传输时序1.2.1.1 Write transfer with no wait states1.2.1.2 Write transfer with wait states 1.2.2 APB 读时序1.2.2.1 Read transfer with no wa…

C语言二级指针复习

之前写过一篇二级指针的博文&#xff0c;C语言二级指针Demo - Win32 版本_bcbobo21cn的博客-CSDN博客 下面复习一下二级指针&#xff1b; 二级指针的概念是这样&#xff0c; int a 100; int *p1 &a; int **p2 &p1; 有一个整型变量a被赋值100&#xff1b;…

【Python开发】FastAPI 10:SQL 数据库操作

在 FastAPI 中使用 SQL 数据库可以使用多个 ORM 工具&#xff0c;例如 SQLAlchemy、Tortoise ORM 等&#xff0c;类似 Java 的 Mybatis 。这些 ORM 工具可以帮助我们方便地与关系型数据库进行交互&#xff0c;如 MySQL 、PostgreSQL等。本篇文章将介绍如何使用 SQLAlchemy 来完…

osgb倾斜摄影三维模型数据web端在线管理平台,一键查看、分享

「四维轻云」是基于浏览器打造的一款osgb倾斜摄影三维模型数据web端在线管理平台&#xff0c;为用户提供了项目管理、团队管理、空间测量、场景编辑、在线标绘等功能&#xff0c;实现了osgb倾斜摄影三维模型数据在线管理、浏览和分享。 此外&#xff0c;为了更好地满足用户需求…

数据库信息速递 AWS因迁移PostgreSQL DBaaS而遭遇长时间停机时间而备受诟病

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

分享:win10使用 python 让 NVIDA GeForce MX250 显卡发挥余热,安装CUDA,cuDNN和PyTorch

目录 1. 更新最新的显卡驱动2. 安装CUDA3. 安装cuDNN4. 安装pytorch 1. 更新最新的显卡驱动 打开NVIDA更新驱动的官网地址 根据下图的选择&#xff0c;记得Windows驱动程序类型要选标准&#xff0c;如图 点击搜索&#xff0c;下面就会列出一大堆的历史驱动&#xff0c;选择第…