前言
去年因云原生和K8S的机缘,参加了天池的“第四届全球数据库大赛赛道2:分布式NewSQL性能挑战”,本以为打酱油,却幸运的获得了第十名。今年本来是在等着参加第五届数据库大赛,却先等来了云原生编程挑战赛,得以首次参加云原生比赛。还是以打酱油的心态,还是很幸运,最终取得了第七名。
一、赛题解析
赛题详见阿里云官网云原生编程挑战赛页面(https://tianchi.aliyun.com/competition/entrance/532103/information)。
本赛题需要选手实现一个 Scaler 模块的功能。在实际生产环境中一个 Scaler 模块为了实现弹性伸缩的功能,需要解决很多工程问题,而具体的工程问题往往和实际的环境和技术栈相关。所以本赛题通过仿真框架屏蔽了这些因环境而异的繁琐工程细节,选手只聚焦在 Scaler 核心逻辑即可。
选手需要实现一个遵循gRPC协议的Scaler, Scaler 是一个 grpc 的服务, 需要实现对外的两个接口Assign和Release,如下所示:
Assign: 为请求分配一个应用实例,一个应用实例只能被分配给一个请求,如果无应用实例存在则需要冷启动,重新创建一个实例。无论是Kubernetes中Pod 还是 FaaS 函数,应用实例的启动过程都包括两个部分:实例的分配(CreateSlot)以及应用环境初始化(Init)。然后,实例才可以正常的处理请求。
Release: 释放请求占用的应用实例,这个实例如果没有被回收, 可以被下次调用请求复用
二、解题思路
基线版本Idle时并不马上释放slot资源,而是通过gcLoop方法进行资源释放,当slot存活时间大于5分钟时才释放它。
这样,Assign时如果有空闲的资源就可以直接复用,提高了复用率,减少了冷启动时间。
但是由于slot存活了5分钟,如果此时请求比较少时,复用率就会比较低,资源的使用时长就会增加,因此,需要调整框架(Scaler)达到请求的时延和资源使用量的最佳均衡。
1.gcLoop方法优化
gcLoop方法是由scaler内部定期调用的,用于回收空闲的资源实例。
gcLoop方法会定期运行,每次运行时,会遍历idleInstances列表,检查每个实例的空闲时间,如果有实例的空闲时间超过了配置的时间IdleDurationBeforeGC,那么这个实例就会被销毁。在销毁实例时,会调用platformClient的DeleteSlot方法删除这个slot,然后从instances映射和idleInstances列表中删除这个实例,最后会更新实例的状态为deleted。
这个方法的运行时间间隔由Config对象的GcInterval字段控制。
选手可以通过配置GcInterval 和 IdleDurationBeforeGC来控制gcLoop方法的运行频率和空闲实例的回收时间。
基线版本GcInterval为10秒,IdleDurationBeforeGC为5分钟,通过对3个数据集分析和实测,不同数据集最优IdleDurationBeforeGC不同
数据集1最优IdleDurationBeforeGC为1秒
数据集2最优IdleDurationBeforeGC为2分钟
数据集3最优IdleDurationBeforeGC为5秒
基于此,我们可以设计以下算法:
默认GcInterval为1秒,IdleDurationBeforeGC为5秒,可以根据请求可能slot存活时间的分布预测,动态的调整IdleDurationBeforeGC值,以达到最优的分值。定义assTimeList,记录每个Assign请求的时间,当有一个请求来时把当前时间push进去。每隔5分钟统计一次所有请求的可能slot存活时间,遍历assTimeList,
可能slot存活时间= 当前请求时间 – 前次请求时间 – 运行时间
如果可能slot存活时间大于0时,我们可以统计以下几钟情况:
小于1秒
1秒—5秒
5秒—2分钟
大于2分钟
统计5分钟内这几种请求的次数,以此来预测后续请求的slot存活时间,再动态调整IdleDurationBeforeGC。比如说:如果5秒–2分钟的请求比较多时,可以动态把IdleDurationBeforeGC调整为1分钟。
2.Assign优化
当来一个请求时,如果有一个同类请求即将释放,那么这个请求可以稍微等待一下,复用即将释放的请求的slot资源,这样可以提升两个得分项,既复用了资源又不会增加slot存活时间。
我们定义一个运行队列,每次来一个请求时,记录它的instance,设置是否正在等待标志为false,并为它分配一个条件变量。当这个请求释放时,把它从运行队列删除,并唤醒条件变量。
每来一个请求时,先判断是否有空闲的资源可用,如果有,直接复用空闲的资源。如果没有空闲的资源时,先不急着创建,而是遍历运行队列,找到第一个没有被等待的请求,等待它释放,此时设置它的等待标志为true,以该类请求的最大运行时间作为超时时间,如果该类请求的最大运行时间大于500毫秒,那就以500毫秒作为超时时间。在超时时间内等待到请求释放则复用,没有等待到再创建资源。
总结
总结参赛的过程,比去年轻松很多,但也因为投入不多,本可以取得更好的成绩却只停留在第七名,未能进入前6参加答辩,也留了一个遗憾。继续努力吧,争取下届能取得更好的成绩。
下面是我的微信公众号,欢迎大家关注