前言
在复杂的分布式系统中,存在着各种性能指标,比如系统请求数,请求响应时间等等。这些指标在一定程度上可以反映出系统运行的快慢程度。但是这里我们如何做到更加准确的判断,而不是说只要出现异常指标,就认定系统有问题,显然这是不合理的。今天,笔者来为大家讲述基于滑动窗口的性能比较算法。如何收集,利用历史数据,来进行当前性能指标的比较。
基于滑动窗口的数据采集
当我们说系统出现“变慢”现象的时候,这个其实是和“过去的时间”作对比,所以我们感觉到它有点慢。同样的,我们要想更加科学地比较这其中的性能差异,就需要用到历史数据。
对于历史数据而言,因为时间是连续的,所以我们要对时间做分片,也就是说,是一段,一段的。这一段的周期可以是10分钟,或半小时等等。在这里,我们用更加专业的词语描述,就叫窗口。每个窗口对应一定的时间区间,随着时间向前滑动。对于每个窗口内,都会有对应时间区间内的性能统计指标数据,比如说我们有该窗口内的总请求数,以及总耗时,这个时候我们可以求出这个窗口的平均响应时间。
那么有了这些窗口数据,我们如何去使用这些数据呢?一个重要的原则是保证数据指标的平滑性。简单地说,我们不能简单暴力地直接使用上个窗口的数据,然后规定出一个规则,比如超出上个指标多少多少倍以上,当前系统就认定为“慢”的。
一种更平滑的做法是,在当前窗口即将结束时,获取到上个窗口的数据,乘上衰减因子,再叠加当前窗口的即时数据,然后把这2个数据的和作为新的“上个窗口”的指标数据。等这个时间窗口过去了,这个衡量阈值就是刚刚过去的窗口的指标平均值。
图示过程如下:
通过以上步骤算出的上个窗口的性能数据,就可以拿来与当前数据进行比较,如果数值超过前面的阈值数据,就表明,系统变得异常了。
基于滑动窗口的衰减算法
下面是基于滑动窗口的衰减算法(以系统响应时间为衡量指标),大家可以对照上面笔者阐述的过程。
/**
* 在当前窗口的尾声阶段,做窗口的滑动
* @param enableDecay
*/
void updateAverageResponseTime(boolean enableDecay) {
for (int i = 0; i < numLevels; i++) {
double averageResponseTime = 0;
// 获取当前窗口的指标数据,算出平均响应时间
long totalResponseTime = responseTimeTotalInCurrWindow.get(i);
long responseTimeCount = responseTimeCountInCurrWindow.get(i);
if (responseTimeCount > 0) {
averageResponseTime = (double) totalResponseTime / responseTimeCount;
}
// 获取上个窗口的数据
final double lastAvg = responseTimeAvgInLastWindow.get(i);
if (lastAvg > PRECISION || averageResponseTime > PRECISION) {
if (enableDecay) {
// 算出新的值,当前平均时间+上个窗口的衰减值得到
final double decayed = decayFactor * lastAvg + averageResponseTime;
// 新的值作为上个窗口的新的数据值
responseTimeAvgInLastWindow.set(i, decayed);
} else {
// 不考虑衰减的情况
responseTimeAvgInLastWindow.set(i, averageResponseTime);
}
} else {
responseTimeAvgInLastWindow.set(i, 0);
}
responseTimeCountInLastWindow.set(i, responseTimeCount);
if (LOG.isDebugEnabled()) {
LOG.debug("updateAverageResponseTime queue: {} Average: {} Count: {}",
i, averageResponseTime, responseTimeCount);
}
// 重置当前窗口数据,准备下个窗口的数据统计
responseTimeTotalInCurrWindow.set(i, 0);
responseTimeCountInCurrWindow.set(i, 0);
}
}
然后将此衰减操作,放在定时器里,就能模拟出滑动窗口的效果了。