问题的提出
如果使用过grafana的人,经常会使用多月对比来做一些展示,会把多月的数据放在同一个页面中。这就要求grafana要把多月的数据都展示出来,在监控中我们一般采集数据的频率会特别低,起码是秒级别的,例如3秒采集一次。采集间隔是根据数据的重要性,以及业务内容来的。我们就按照1分钟一个点来算,1个月就是2592000个点,如果在浏览器上画出2592000个点,估计浏览器渲染的过程中就卡死了。
我们带着这个疑问就开始看看grafana是怎么做的。(以prometheus为例)
step的功能
先介绍一下prometheus的http api中的step。他接收一个秒作为时间单位,例如2秒这种,prometheus会根据传入的时间长度进行整理和采样,例如一个时间序列,10秒一个点,1分钟会有6个点。如果我传递step为60秒,prometheus只会返回1个点。如果算出这一个点的方式,需要看prometheus的源码了,这里就不细说了。
step自适应实现
prometheus已经有api可以做采样和分析,那么在grafana这边,做到根据时间范围,来做step的自适应就完全可以了。我们接下来看看他是怎么做的自适应。
grafana本身是拿点的个数来做限制的,他的代码里写了1500个。也就是说一个图上最多最多也就是1500个点,grafana要解决的问题是,在用户所选择的时间范围内,如何更合适的找出这1500个点。最简单的思路就是平均,把时间段的选择,平均分散在1500个格子,看每个格子里的时长是多少。
举个例子,要查1500s的间隔数据,正好1500个格子,所以一个格子里是1秒,我们只要选择1秒一个点,就可以满足这个上限。
grafana也是这么做的。而且他还做了时间片对齐(就是不会算下来出现小数点特别长的情况)。
func roundInterval(interval time.Duration) time.Duration {
switch true {
// 0.015s
case interval <= 15*time.Millisecond:
return time.Millisecond * 10 // 0.01s
// 0.035s
case interval <= 35*time.Millisecond:
return time.Millisecond * 20 // 0.02s
// 0.075s
case interval <= 75*time.Millisecond:
return time.Millisecond * 50 // 0.05s
// 0.15s
case interval <= 150*time.Millisecond:
return time.Millisecond * 100 // 0.1s
// 0.35s
case interval <= 350*time.Millisecond:
return time.Millisecond * 200 // 0.2s
// 0.75s
case interval <= 750*time.Millisecond:
return time.Millisecond * 500 // 0.5s
// 1.5s
case interval <= 1500*time.Millisecond:
return time.Millisecond * 1000 // 1s
// 3.5s
case interval <= 3500*time.Millisecond:
return time.Millisecond * 2000 // 2s
// 7.5s
case interval <= 7500*time.Millisecond:
return time.Millisecond * 5000 // 5s
// 12.5s
case interval <= 12500*time.Millisecond:
return time.Millisecond * 10000 // 10s
// 17.5s
case interval <= 17500*time.Millisecond:
return time.Millisecond * 15000 // 15s
// 25s
case interval <= 25000*time.Millisecond:
return time.Millisecond * 20000 // 20s
// 45s
case interval <= 45000*time.Millisecond:
return time.Millisecond * 30000 // 30s
// 1.5m
case interval <= 90000*time.Millisecond:
return time.Millisecond * 60000 // 1m
// 3.5m
case interval <= 210000*time.Millisecond:
return time.Millisecond * 120000 // 2m
// 7.5m
case interval <= 450000*time.Millisecond:
return time.Millisecond * 300000 // 5m
// 12.5m
case interval <= 750000*time.Millisecond:
return time.Millisecond * 600000 // 10m
// 12.5m
case interval <= 1050000*time.Millisecond:
return time.Millisecond * 900000 // 15m
// 25m
case interval <= 1500000*time.Millisecond:
return time.Millisecond * 1200000 // 20m
// 45m
case interval <= 2700000*time.Millisecond:
return time.Millisecond * 1800000 // 30m
// 1.5h
case interval <= 5400000*time.Millisecond:
return time.Millisecond * 3600000 // 1h
// 2.5h
case interval <= 9000000*time.Millisecond:
return time.Millisecond * 7200000 // 2h
// 4.5h
case interval <= 16200000*time.Millisecond:
return time.Millisecond * 10800000 // 3h
// 9h
case interval <= 32400000*time.Millisecond:
return time.Millisecond * 21600000 // 6h
// 24h
case interval <= 86400000*time.Millisecond:
return time.Millisecond * 43200000 // 12h
// 48h
case interval <= 172800000*time.Millisecond:
return time.Millisecond * 86400000 // 24h
// 1w
case interval <= 604800000*time.Millisecond:
return time.Millisecond * 86400000 // 24h
// 3w
case interval <= 1814400000*time.Millisecond:
return time.Millisecond * 604800000 // 1w
// 2y
case interval < 3628800000*time.Millisecond:
return time.Millisecond * 2592000000 // 30d
default:
return time.Millisecond * 31536000000 // 1y
}
这个代码真的是很辛苦,他枚举了各种情况的时间分片。
最佳实践
既然grafana做了step的自适应,这样对我们能产生什么影响呢?
prometheus有各种函数表达式。他可以根据时间段来做计算。例如2分钟的差值,速度等等。但是这个时间在语句里是写死的。例如写死了2s的计算,当我的step只要不是2s,那么就会出现问题,小于2s的时候,计算会重叠,大于2s的时候会漏算数据。
grafana通过内置变量的方式,提供了解决方案。$__interval, $__interval_ms他内置了这两个变量代表step,第一个是带时间单位的,展示出来是2s,1m,1d这种,第二个是毫秒值,不带单位,1000,2000。但是可以再后面拼接一些单位。一般把$__interval传入就可以解决上面的问题了,第二种表达在一些特定的扩大倍数的场景会有作用。
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章