关于《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:language和business-service-type
- 下面的表格说明了nginx、tomcat、mysql这些pod的两个label的具体值
pod | 语言类型(language) | 服务类型(business-service-type) |
---|---|---|
nginx | c | web |
tomcat | c | web |
mysql | java | storage |
- 根据上述表格可见,nginx、tomcat、mysql这些pod,一共有两种分类方式:
- 按照语言类型分类,nginx和mysql都是c语言类,tomcat是java类
- 按照服务类型分类,nginx和tomcat都是web类,mysql是存储类
- 请记住上述分类方式,接下来可以列出业务需求了
- 根据指定语言查询对应的pod的key,例如输入java,返回tomcat的pod的key
- 根据指定的pod的key查询pod对象,例如输入indexer-tutorials/nginx-deployment-87945df85-vzjgv,返回nginx的pod对象
- 查询指定语言的所有对象,例如输入java,返回tomcat的pod
- 根据某个对象的key,获取同语言类型的所有对象,例如输入indexer-tutorials/nginx-deployment-87945df85-vzjgv,返回的就是nginx和mysql的所有pod,因为它们的语言类型都是c
- 返回所有语言类型,这里应该是c和java
- 返回所有分类方式,这里应该是按服务类型和按语言类型两种
- 接下来开始编码啦,用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)
}
- 上述代码有以下几处需要注意
- 注意区分Indexers和Indexer,两者不是同一个东西,Indexers是个map,Indexer提供了各种查询API,Indexer是本篇我们要重点关注的内容
- 对应Indexers,key是分类方式的名称,这个可以自己起名字,value是方法,用于自定义如何设定该对象在此分类下的的结果,举个例子,如果按照语言分类,那么key等于"indexer_language",value是个方法,该方法用于返回一个pod的label值,该label就是"language"
- 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相信您已经掌握了,接下来咱们会进入性能篇,来看看这个理论上的高性能在实际环境中有着怎样的表现