评分模型可以简单描述为:
score=sum(评分项*权重)
选取评分项边界
评分模型的评分项确认之后,为了防止单项分数过高,需要对评分项进行百分化,并且所有权重总和为 100 ,根据评分项计分模型可以算出符合增长曲线的分数,这样评分模型计算出来的总分数为 100 ,故需要确认每项的分数边界、权重、计分模型。只有各项的边界、权重、计分模型确认之后,给定一个慢查询,评分模型才能计算出合理的分数。评分项的边界可以根据当前历史数据设置。计分模型和权重可以首先进行假设,测试完成之后如果不符合预期则修改权重、计分模型,并重复测试-修改过程,直至测试结果符合预期。
边界选取标准
根据当前慢查询的历时记录,由于极值数据可能会存在干扰,导致真实值失真,故需要去除最高部分 5% 的异常值,将 95 分位的值作为每个评分项的最高边界。如果单项值超过最高边界的值评分项,单项分数都将被设置为最大分数。
查询时间:
95 分位的 sql 慢查询耗时约在 60s 左右
锁等待时间
95分位的慢查询锁等待时间约为0.00629s
扫描行数
95分位的慢查询扫描行数约为1785w行
查询次数
95分位的慢查询次数约为180个
发送流量
由于流量字段缺失,暂时不计入评分系统。
计分项边界值
计分模型
每一项计分项的边界得以确认,值越大分数越高。但是存在以下情况:
某些评分项的值对系统的影响程度并不是成正比例,超过某个临界点,对系统的压力会迅速增长。
比如:查询次数,一条超时为1s的sql,查询1次、查询10次、查询100次,对系统的压力是不一样的,量变会引发质变。
设计有一下四种计分模型:
计分代码如下:
/** * @Description: 计算单项得分,分数介于最小分数和最大分数之间,可选的计分模型有:类正弦模型、正弦模型、指数模型、正比例模型 * @Param val: 单项当前值 * @Param minVal: 单项最小值 * @Param maxVal: 单项最大值 * @Param minScore: 单项最小得分 * @Param maxScore: 单项最大得分 * @Param calWay: 计分模型方式 * @Return float64: 单项得分 */ func calSingleScore(val, minVal, maxVal, minScore, maxScore float64, calWay string) float64 { if maxVal == 0 { // 如果值为0则返回0 return 0 } if val >= maxVal { // 如果值超过上边界,则设置为最大分数 return maxScore } if val <= minVal { // 如果值低于下边界,则设置为最小分数 return minScore } var scoreRatio float64 switch calWay { case "likeSin": // 类正弦曲线 // Y = a + b·X + c·X2 + d·X3 + e·X4 + f·X5 b := 0.0547372760360247 c := -0.0231045458864445 d := 0.00455283203705563 e := -0.000281663561505204 f := 5.57101673606083e-06 // 使用20个函数绘制点位拟合出来的 ratio := (val - minVal) / (maxVal - minVal) * 20 scoreRatio = b*ratio + c*(ratio*ratio) + d*(ratio*ratio*ratio) + e*(ratio*ratio*ratio*ratio) + f*(ratio*ratio*ratio*ratio*ratio) case "sin": // 正弦曲线 ratio := (val - minVal) / (maxVal - minVal) scoreRatio = math.Sin(math.Pi / 2 * ratio) case "exponent": // 指数曲线 ratio := (val - minVal) / (maxVal - minVal) a := math.Log2(maxScore - minScore) scoreRatio = math.Pow(2, a*ratio) return scoreRatio default: // 默认是正比例 scoreRatio = (val - minVal) / (maxVal - minVal) } return scoreRatio * (maxScore - minScore) }
模型曲线如下: