目录
引言
问题场景
尝试解决
deriv函数解析
最终解决
总结与思考
引言
Promethues是k8s以及云原生下的标准监控告警系统,提供了很多内置的函数,功能已经十分强大,但是,依然有一些需求不能直接使用内置函数来解决。比如,Promethues中计算变化率可以使用内置的rate或者irate函数,但是这两个函数只能作用于counter类型,如果是guage类型,则没有同样功能的内置函数来实现。笔者在实际工作中遇到了类似的问题,本文记录了整体的解决过程和思路。
期望直接看解答的,可以直接查看最下面最终解决部分。
问题场景
用户有一个qps指标,记录的是当前的瞬时qps,使用的是guage类型。用户侧期望配置一个监控,能够在qps出现剧烈波动的情况下发出告警,具体规则是突增或突降30%以上。
尝试解决
对于Promethues变化率,百度和google可以搜到很多,不过都是rate的用法。官方文档上也明确说了,rate和irate只适用于counter类的指标。
rate
should only be used with counters and native histograms where the components behave like counters.
irate
should only be used when graphing volatile, fast-moving counters.
又google了一下,发现How to calculate the instant rate of a guage metric中指出可以使用deriv函数来解决。
百度和google查了一圈,除了官方文档中的解释和一些中文翻译,几乎没有deriv函数相关的资料,那么只能实际的测试一把了。
使用下面的公式:
deriv(sum({__name__=~"current_qps"})[1m])
得到结果:
看起来也还比较符合趋势,就没有进行深究,设置了对应的告警策略(加上abs取绝对值):
abs(deriv(sum({__name__=~"current_qps"})[1m])) > 30
加上告警后,因为触发的比较多,所以对比了计算触发告警两个点之间的差值,发现不够30%,差距较大。
deriv函数解析
因为监控告警不准,所以要认真解析下deriv函数了。同时,由于网上资源过于稀少,所以只能从源码入手了。
查看Promethues源码,functions.md中指出:
## `deriv()`
`deriv(v range-vector)` calculates the per-second derivative of the time series in a range
vector `v`, using [simple linear regression](https://en.wikipedia.org/wiki/Simple_linear_regression).
The range vector must have at least two samples in order to perform the calculation. When `+Inf` or
`-Inf` are found in the range vector, the slope and offset value calculated will be `NaN`.
`deriv` should only be used with gauges.
deriv实际上是计算了范围向量每秒的导数,根据高中数学知识(已经忘完了,重新百度的)导数是平面上点切线的斜率,因此我理解这个deriv求出的应该是斜率。那具体是怎么计算斜率呢,文档中指出,计算依据的数学方法是简单线性回归:
看到这里,因为数学知识的匮乏和长久不用的生疏,盲目相信Stack Overflow文章 的解答,自己直接认为导数==斜率==比率,为了验证自己的想法,继续查看Promethues的源码,看看是如何计算的:
funcDeriv中首先获取样品值,数据少于2的话直接return,关键的处理步骤是:
slope, _ := linearRegression(samples.Points, samples.Points[0].T)
Matrix的定义:
Series的定义:
继续查看linearRegression 代码:
基本上就是简单线性回归求斜率的实现,其中涉及到了一个kahanSumInc函数,这里笔者一开始认为是逻辑处理,后来查询资料发现是一个公共的Kahan求和算法,查询了一下kahan求和算法:
回到Promethues的工程实现:
学到这里,也是有所收获,以后如果遇到处理多float类型的加和精度损失问题,可以借鉴与参考,尤其是一些涉及到钱的功能。
而至于deriv函数,其得出的值确实是导数,或者说是斜率。它可以代表指标变化的加速度,但是无法代表变化率,因此想要求变化率,还得另辟蹊径。
最终解决
我们知道,变化率=(X2-X1) / X1,对于Promethues的指标来说,我们可以拿到当前的值X2,据我的了解,可能不全面,没有函数和方法能直接拿到前一个时刻X1的值,但是,我们可以通过delta函数来拿到差值,也就是说:
delta=X2-X1
所以:
X1=X2-delta
所以:
变化率=delta / (X2 - delta)
最终的监控指标写法:
1、如果要监控最近两个数据时间点的变化率,类似于irate,使用以下方法:
abs(idelta(sum({__name__=~"current_qps"})[1m]) / (sum({__name__=~"current_qps"})- idelta(sum({__name__=~"current_qps"})[1m]))) > 0.3
2、如果要看1min平均的,类似于rate,使用以下方法:
abs(delta(sum({__name__=~"current_qps"})[1m]) / (sum({__name__=~"current_qps"})- delta(sum({__name__=~"current_qps"})[1m]))) > 0.3
在线上验证了下,数据符合预期。
总结与思考
遇到问题我们习惯于百度与google,而不是自己分析。google出来的答案不一定是合理的,而且由于大部分是英文的,理解也可能有偏差。因此,对于问题和需求,在google出的资料不是特别符合的情况下,还是要从需求本身出发,转换思维模式,利用手头上有的资源来实现我们的目的。