# README
cpu-massager-go
cpu-massager是一个过载保护器,称为"CPU按摩器"。
本仓库提供一个用go语言实现用来给CPU做马杀鸡的按摩器,利用该按摩器,可以设定一些参数,根据CPU的状态和所设参数,动态调整拒绝服务的概率,服务程序使用按摩器提供的相关API来决定对收到请求的应对方式:是照常处理还是拒绝服务,以此来让CPU的使用率维持在一个合适的水位,避免服务雪崩。
目录
使用方法
分两步:
- 调用StartMassagePlan这个API启动按摩计划;
- 调用NeedMassage这个API判断是否要拒绝服务。
启动按摩计划
在程序启动的时候调用StartMassagePlan。例如,在Linux环境下可以使用默认参数直接调用:
func main() {
err := cpumassager.StartMassagePlan()
if err != nil {
handleError() // 处理出错的情况,一般打印一下出错信息
os.Exit(1) // 然后退出就好了
}
serve() // 进入服务程序正常处理流程
}
有需要调整相关参数的可以使用WithXXX系列API来设定相关参数启动按摩计划,具体参数的说明,可以参照代码中对于options结构的注释。
判断是否拒绝服务
程序启动,接收到请求,开始处理之前,先调用NeedMassage这个API来决定是正常处理该请求还是拒绝为其服务返回过载的错误信息。
func handleARequest() {
if cpumassager.NeedMassage() {
refuse() // 拒绝服务该请求,做一些简单的处理,例如设定回包的错误码,上报过载告警等
return // 然后直接返回
}
process() // 正常处理该请求
}
工作原理
按摩器分为如下几个部分:
- 提供给服务程序调用的API,具体可以参照"使用方法"部分的说明;
- 按摩器内部的CPU使用率收集器、记录器和用于判断是否需要拒绝服务的决断器。
按摩器涉及模块的示意图:
CPU使用率收集器
服务程序启动按摩计划之后,按摩器就会启动一个routine来每隔1秒钟定期收集CPU使用率。收集器以一个接口的形式提供,不同的操作系统对于CPU使用率的取用方法可能会不一样,可以根据具体情况来提供具体的实现。
Linux平台上CPU使用率,读取procfs(进程文件系统)中的"/proc/stat"文件得到当下的CPU时间,取一个时间段前后的差值就可以得到。具体的可以参照htop的源码LinuxProcessList_scanCPUTime
CPU使用率收集器示意图:
CPU使用率记录器
记录器用来记录收集器收集到的CPU使用率数据。记录器并不记录所有的CPU使用率数据,而是维护了一系列计数器,每个CPU使用率采集周期,都对每个计数器进行调整:
- 如果CPU使用率数据>=当前计数器对应的CPU使用率,那么将该计数器加1,最高加到100;
- 如果CPU使用率数据<当前计数器对应的CPU使用率,那么将该计数器减1,最低减至0。
由于每个计数器的范围是[0, 100],这样就维护了最近100个采集周期(也就是最近100秒)的CPU使用率在不同水位的占比情况。例如,如果">=80计数器"的数值是75,那么就表示最近100次采集数据中,有75次CPU使用率不低于80%。维护这样的计数可以避免某[几]次的CPU使用率统计数据可能的误差,用一段时间内的集聚效果来确保获取到最近一段时间的CPU使用率真实水准。
CPU使用率记录器示意图:
处理方式决断器
处理决断器,用来决定每个请求的处理方式:
- 当CPU处在空闲的 轻松 状态时,每个请求都需要正常处理;
- 当CPU处在繁忙的 疲累 状态时,需要根据设定参数以动态变化的概率拒绝为相关请求服务。
判断CPU高低负荷
在具体判断CPU的"轻松"和"疲累"状态时,需要在每个CPU使用率采集时间点计算当下的CPU高低负荷,这个是根据启动按摩计划传入的两个参数和CPU记录器的计数器读数计算得到的:
- highLoadLevel,高负荷等级,这个是和CPU使用率记录器的计数器相匹配的,按摩器在判断CPU状态的时候,会根据该等级获取对应的计数器读数;
- highLoadRatio,高负荷比例,这个需要和高负荷等级配合使用,如果CPU使用率记录器中对应高负荷等级的计数器读数的占比高于高负荷比例,则认为CPU处于 高负荷 中,否则认为CPU处于 低负荷 中。
切换CPU状态
CPU状态由轻松到疲累状态比较简单,CPU使用率采集器每个定期采集动作都会判断CPU是否高负荷,如果是在低负荷时检测到高负荷,直接将CPU状态由"轻松"转变成"疲累"即可。
由疲累到轻松的切换,相对复杂一些,先说明一个 intensity-按摩力度 的概念,按摩力度是指拒绝服务的概率,以百分比表示,取值范围是[0, 100],例如,50表示以50%的概率拒绝服务。
还需要说明一个 CPU持续高/低负荷 的概念,这个会作为CPU疲累程度调整的依据,持续高/低负荷只有CPU处于疲累时才有意义,由每个检查周期得到的highLoadLevel对应计数器读数决定:
- 在疲累状态下检测到当前CPU处于高负荷,且本次检查周期得到的highLoadLevel对应计数器读数和上周期相比提高了,那么就认为 CPU持续高负荷;
- 在疲累状态下检测到当前CPU处于低负荷,且本次检查周期得到的highLoadLevel对应计数器读数和上周期相比降低了,那么就认为 CPU持续低负荷;
- CPU每次由轻松转变到疲累或者调整按摩力度的时候都会重置changeIntensityTime为currentCPUsageRecordTime。
具体做疲累到轻松的切换时,会根据每个采样动作的判断结果,评估CPU是否持续高/低负荷,以此来对CPU的按摩力度做动态提高或降低,在合适的时机(CPU的按摩力度降为0的时候)才能进入轻松状态,调整按摩力度的时候,需要考虑启动按摩计划时传入的相关参数:
- initialIntensity,初始化按摩力度,表示当CPU初次进入疲累状态时候拒绝服务的概率;
- stepIntensity,按摩力度调整步进值,如果CPU进入疲累状态后持续处于高负荷/低负荷状态的时间超过配置的检查周期,会以此参数来增加/减少实际的按摩力度;
- currentIntensity,实际的按摩力度;
- checkPeriodInSeconds,调整按摩力度的检查周期,在CPU处在疲累状态下,CPU状态持续时长超过该值,则会调整按摩力度。
切换CPU状态示意图:
疲累时拒绝服务
CPU处于疲累状态时,会根据如下几个方式来决定是否需要拒绝服务:
- todoTasks,待处理任务数,疲累状态下每调用一次NeedMassage()就会加1;
- doneTasks,已完成任务数,疲累状态下每次调用NeedMassage()返回false的情况下就会加1;
- requireTasks,需要完成的任务数,用如下公式计算得到:todoTasks * (100 - currentIntensity) / 100;
- 如果doneTasks < requireTasks,则需要提供服务,否则拒绝服务。
这种拒绝服务的方式,基于本地的信息作出决断,算法也非常简单,可以在不增加额外依赖的情况下,提供均匀的拒绝概率。配合前面的动态按摩力度,达到了在过载时候动态维护高服务水准的目的。