client-go的Indexer三部曲之二:性能测试

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):github.com/zq2599/blog...

本篇概览

  • 本文是《client-go的Indexer》系列的第二篇,在前文咱们通过实例掌握了client-go的Indexer的基本功能,本篇咱们尝试对下面这两种接口进行压力测试,用数据验证Indexer的性能优势,看看是否如理论分析那样真的存在
  1. 第一个接口:basic/get_obj_by_obj_key,这个接口会用到Store.GetByKey方法,从本地缓存中取得pod对象返回,如下图红色箭头所示
  1. 第二个接口:remote/get_obj_by_obj_key_remote_query,此接口会调用client-go库的api,向api-server发起http请求,查找指定pod的信息返回

部署情况说明

  • 今天的压测所涉及的服务和应用,它们的部署情况如下图所示,共两台Ubuntu电脑,电脑1用于执行压测,上面部署了k6(或者部署docker,用docker来运行k6),电脑2部署了kubernetes,同时也运行着名为client-go-indexer-tutorials的应用,该应用就是咱们编写的代码:实现了今天要压测的两个接口

源码下载

名称 链接 备注
项目主页 github.com/zq2599/blog... 该项目在GitHub上的主页
git仓库地址(https) github.com/zq2599/blog... 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在tutorials/client-go-indexer-tutorials文件夹下,如下图红框所示:

编码(第一个接口:basic/get_obj_by_obj_key)

  • 第一个接口的源码其实在前文其实已写好,就不重新写了,这里看一下关键代码,如下所示,其实也就一行(调用INDEXER.GetByKey)
go 复制代码
// 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")
		}
	}
}
  • 在初始化gin的时候绑定path和handler即可
go 复制代码
	// 用于提供基本功能的路由组
	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)

编码(第二个接口:remote/get_obj_by_obj_key_remote_query)

  • 要使用client-go库,首先要准备好ClientSet对象,这个在前文也准备好了,放在全局变量中随时可以用,来回顾初始化的代码
go 复制代码
var ClientSet *kubernetes.Clientset
var once sync.Once

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())
	}
	...
  • 再来看如何用ClientSet向api-server发起请求,这也是熟悉的api,在《client-go实战》系列中屡屡用到
go 复制代码
// GetObjByObjKey 远程请求,根据指定key查询pod对象
func GetObjByObjKey(c *gin.Context) {
	rawObj, err := basic.ClientSet.
		CoreV1().
		Pods(basic.NAMESPACE).
		Get(c, c.DefaultQuery("pod_name", ""), metav1.GetOptions{})

	if err != nil {
		c.String(500, fmt.Sprintf("g. get pod failed, %v", err))
	} else {
		c.JSON(200, rawObj)
	}
}
  • 最后是绑定path和方法
go 复制代码
	remoteGroup := r.Group("/remote")
	// g. 使用clientset远程查询
	remoteGroup.GET("get_obj_by_obj_key_remote_query", remote.GetObjByObjKey)
  • 请将程序运行起来,稍后压测会用到
  • 至此,用于性能对比测试的两个接口代码都已经写好,接下来开始准备性能测试

用k6压测第二个接口(远程访问api-server的方式)

  • 这里用到k6作为压测工具,您也可以选择自己熟悉的工具来用,选择k6是因为足够简单省事儿,如果您已经装好了docker,执行压测只要一行命令就行了
  • 首先编写第二个接口的压测脚本,也就是压测client-go远程访问api-server查询对象的性能,新建文件remote.js,内容如下,可见非常简单,就是发请求检查返回码和返回body
shell 复制代码
import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const res = http.get(`http://${__ENV.MY_HOSTNAME}/remote/get_obj_by_obj_key_remote_query?pod_name=${__ENV.POD_NAME}`);
  check(res, {
    'is status 200': (res) => res.status === 200,
    'body size is > 0': (r) => r.body.length > 0,
  });
}
  • 如果您的电脑上部署了docker,那么执行以下命令即可完成压力测试,命令中的参数稍后会详细说明
shell 复制代码
docker run \
--rm \
-i \
loadimpact/k6 \
run \
--duration 60s \
--vus 10 \
-e MY_HOSTNAME=192.168.50.76:18080 \
-e POD_NAME=nginx-deployment-696cc4bc86-2rqcg \
- < remote.js
  • 这里解释清楚上述命令用到的每个参数,
shell 复制代码
docker run \    	// docker运行容器
--rm \				// 等当前控制台结束时删除该容器(相当于一次性任务)
-i \				// 保持STDIN打开
loadimpact/k6 \		// 镜像名
run \				// 容器中执行的命令,即启动k6的命令
--duration 60s \	// k6的参数:压测时长60秒
--vus 10 \			// k6的参数:并发数为10
-e MY_HOSTNAME=192.168.50.76:18080 \				// remote.js脚本中用到的参数,压测服务的IP和端口
-e POD_NAME=nginx-deployment-696cc4bc86-2rqcg \		// remote.js脚本中用到的参数,pod名称
- < remote.js										// k6压测脚本名称
  • 压测结束,详细数据如下,没有报错,整体QPS为5,虽然我的电脑很烂,但是这么低的QPS还是让人看了直摇头...
shell 复制代码
     ✓ is status 200
     ✓ body size is > 0

     checks.........................: 100.00% ✓ 638      ✗ 0
     data_received..................: 1.5 MB  25 kB/s
     data_sent......................: 53 kB   857 B/s
     http_req_blocked...............: avg=119.67µs min=927ns   med=2.63µs   max=4.2ms    p(90)=4.96µs   p(95)=7.16µs
     http_req_connecting............: avg=115.35µs min=0s      med=0s       max=4.13ms   p(90)=0s       p(95)=0s
     http_req_duration..............: avg=1.9s     min=15.62ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
       { expected_response:true }...: avg=1.9s     min=15.62ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
     http_req_failed................: 0.00%   ✓ 0        ✗ 319
     http_req_receiving.............: avg=244.4µs  min=23.86µs med=115.38µs max=8.72ms   p(90)=343.48µs p(95)=674.31µs
     http_req_sending...............: avg=18.34µs  min=4.59µs  med=12.75µs  max=209.44µs p(90)=25.43µs  p(95)=35.07µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=1.9s     min=15.51ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
     http_reqs......................: 319     5.159646/s
     iteration_duration.............: avg=1.9s     min=19.58ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
     iterations.....................: 319     5.159646/s
     vus............................: 5       min=5      max=10
     vus_max........................: 10      min=10     max=10

用k6压测第一个接口(获取本地缓存的数据)

  • 远程访问api-server的方式,QPS只有5 ,接下来该测试第一个接口了,看走本地缓存在性能上是否能比远程访问更强,这要是翻车了该咋办,博客都写不下去了...,Indexer成了全村的希望
  • 先编写k6脚本,名为indexer.js
shell 复制代码
import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const res = http.get(`http://${__ENV.MY_HOSTNAME}/basic/get_obj_by_obj_key?obj_key=${__ENV.OBJ_KEY}`);
  check(res, {
    'is status 200': (res) => res.status === 200,
    'body size is > 0': (r) => r.body.length > 0,
  });
}
  • 完整的压测命令如下
shell 复制代码
docker run \
--rm \
-i \
loadimpact/k6 \
run \
--duration 60s \
--vus 10 \
-e MY_HOSTNAME=192.168.50.76:18080 \
-e OBJ_KEY=indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg \
- < indexer.js
  • 压测结束,详细数据如下,没有报错,整体QPS为993,嘿嘿,稳了,没翻车...
shell 复制代码
     ✓ is status 200
     ✓ body size is > 0

     checks.........................: 100.00% ✓ 119236     ✗ 0
     data_received..................: 288 MB  4.8 MB/s
     data_sent......................: 10 MB   168 kB/s
     http_req_blocked...............: avg=4.67µs   min=343ns  med=3.13µs  max=4.81ms   p(90)=5.55µs   p(95)=6.56µs
     http_req_connecting............: avg=633ns    min=0s     med=0s      max=4.4ms    p(90)=0s       p(95)=0s
     http_req_duration..............: avg=9.87ms   min=3.15ms med=9.03ms  max=238.05ms p(90)=13.56ms  p(95)=15.55ms
       { expected_response:true }...: avg=9.87ms   min=3.15ms med=9.03ms  max=238.05ms p(90)=13.56ms  p(95)=15.55ms
     http_req_failed................: 0.00%   ✓ 0          ✗ 59618
     http_req_receiving.............: avg=321.15µs min=8.16µs med=70.93µs max=62.14ms  p(90)=478.85µs p(95)=2.07ms
     http_req_sending...............: avg=19.72µs  min=2.29µs med=14.48µs max=2.82ms   p(90)=25.42µs  p(95)=37.31µs
     http_req_tls_handshaking.......: avg=0s       min=0s     med=0s      max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=9.53ms   min=3.07ms med=8.69ms  max=237.72ms p(90)=13.13ms  p(95)=15.04ms
     http_reqs......................: 59618   993.440775/s
     iteration_duration.............: avg=10.05ms  min=3.24ms med=9.21ms  max=238.67ms p(90)=13.75ms  p(95)=15.73ms
     iterations.....................: 59618   993.440775/s
     vus............................: 10      min=10       max=10
     vus_max........................: 10      min=10       max=10
  • 至此,压测完成,同样是获取pod的信息,Indexer由于不涉及网络请求,在性能优势上表现的很明显,当然了Indexer也不是万能了,前文编码中,它的局限性也体现出来了
  1. 要和api-server保持长连接,以获取数据最新的变化
  2. 本地内存中长期存放资源数据,相比之下client-go的一次请求响应就搞定了
  3. 灵活性,下图是client-go远程请求api-server的代码,可以按需求随时改变条件,namespace、label-selector等等自由变化,而Indexer则一开始就要把范围确定好,然后只能本地获取这些资源的内容
  • 综上所述,两种方式各有优劣,就算混合着用也没问题,一切还是为业务服务吧
  • 至此,性能篇就完成了,接下来就是《client-go的Indexer三部曲》的终篇:源码篇,相信经历了前两篇的实战,您对Indexer已经有了很深入的了解,所以接下来阅读源码也就是件很轻松愉快的事情了

参考

欢迎关注掘金:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

相关推荐
ningqw37 分钟前
SpringBoot 常用跨域处理方案
java·后端·springboot
你的人类朋友41 分钟前
vi编辑器命令常用操作整理(持续更新)
后端
胡gh1 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
一只叫煤球的猫2 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
uzong2 小时前
技术人如何对客做好沟通(上篇)
后端
鼠鼠我捏,要死了捏2 小时前
基于Kubernetes StatefulSet的有状态微服务部署与持久化存储实践经验分享
kubernetes·containers·statefulset
颜如玉3 小时前
Redis scan高位进位加法机制浅析
redis·后端·开源
Moment3 小时前
毕业一年了,分享一下我的四个开源项目!😊😊😊
前端·后端·开源
why技术4 小时前
在我眼里,这就是天才般的算法!
后端·面试
绝无仅有4 小时前
Jenkins+docker 微服务实现自动化部署安装和部署过程
后端·面试·github