prometheus-----综合优化

发布时间 2023-06-08 17:02:09作者: 呼长喜

prometheus-----综合优化
1、使用recording rule来优化查询性能(尤其适合于供仪表盘查询的数据)
recording rule允许预先计算经常需要或计算上昂贵的表达式,并将其结果保存为一组新的时间序列。 因此,查询预先计算的结果通常比每次需要时执行原始表达式快得多。 这对于仪表板尤其有用,仪表板需要在每次刷新时重复查询相同的表达式。记录和警报规则存在于规则组中,组内的规则以固定间隔顺序运行。

所以将现有的查询指标进行规则化,然后把grafana中使用的promql替换成简化的rule值,将大大降低性能压力,提高监控效率。同时组内规则按照固定时间间隔运行,间隔时间最好也错开,这样避免查询峰值的出现。

对于页面上展示的热查询, 如果涉及时间序列太多, 则会变得缓慢. 一般涉及时间序列大于 10K 的 InstantQuery 和时间序列大于 1K 的 RangeQuery, 是比较昂贵的。
Prometheus 提供了recording_rule功能, 其会定时如 1 分钟对 promQL 表达式定时执行 instantQuery, 执行结果形成新的时间序列, 数据来自内存 TSDB, 完全内存操作, 性能很高。

2、警惕维度(Cardinality)过高的指标—即:高基数指标问题
label 对于多维监控非常有用,一个指标的基数是指标中所有 label 枚举值组合的笛卡尔乘积. 一个进程中一个指标一千的基数是合理的上限。一个进程的总基数是所有指标的基数之和, 一个进程一万总基数是合理的上限,因此:

label 中不适合放 用户 ID/设备 ID/URL 参数 等高基数的值. 单个 label 值不超过 128 个字符;
避免一个指标过多的 label 组合, 不必要的组合 label 可以拆解为多个指标, 以便降低指标基数, 提高该指标的查询性能

Metrics 更关注系统级别的高效指标而不是单个请求级别, 不要在 Metrics 中放过多的细节 label, 单独 Metrics 无法解决所有的可观测性问题, 详细的信息应记录 Logs 和 Traces 中, 或者在 Exemplar 带上 traceID, 充分利用三大信号 Metrics/Logs/Traces 关联 一起来观测系统

3、PromQL
1、对于 counter, 要先 rate 后 sum, 因为rate/irate/increase 函数才有 counter 跳变检测;

https://prometheus.io/docs/prometheus/latest/querying/functions/#rate
1
2、聚合语句模式中 ([parameter,] ) [without|by ()]

without 是移除特定标签, by 则是保留某些标签. without 能在聚合移除高基数标签的同时保留更多的上下文信息;

4、relabel_configs
提前drop掉一些无用的指标或者非必要的高基数指标

5、recording_rule命名
命名 维度:指标名:聚合方式 , 如 server:rpc_request_started_total:rate5m

6、优化查询性能
1、Prometheus 查询性能与查询语句计算所命中的时间序列数量、样本数以及返回的数据大小 强相关. 正常小查询响应是毫秒级的. 界面展示的大查询(涉及时间序列超过 10k 以上的), 如租户内的所有请求量/server 级别的 CPU 使用列表 这些大查询需要用 recording_rule 定时计算好, 将查询所需的时间序列数降低;

2、展示单个信息或表格使用 instantQuery 即时查询, 只返回最新时刻计算的数据即可. 展示时间图形才需要使用 rangeQuery 范围查询, 返回时间区间内计算的所有数据。

7、警惕Rate 类函数 + Recording Rule 的坑
可能你已经知道了 PromQL 里要先 rate() 再 sum(),不能 sum() 完再 rate()。但当 rate() 已经同类型的函数如 increase() 和 recording rule 碰到一起时,可能就会不小心掉到坑里去。

有了一个维度很高的指标(只能继续维护了,因为没有尽早干掉),为了让大家查询得更快一点,我们设计了一个 Recording Rule,
用 sum() 来去掉维度过高的 bad_label,得到一个新指标。那么只要不涉及到 bad_label,大家就可以用新指标进行查询,Recording Rule 如下:

sum(old_metric) without (bad_label)
1
用了一段时候后,大家发现 new_metric 做 rate() 得到的 QPS 趋势图里经常有奇怪的尖峰,但 old_metric 就不会出现。这时我们恍然大悟:绕了个弯踩进了 rate() 的坑里。

这背后与 rate() 的实现方式有关:

rate() 在设计上假定对应的指标是一个 Counter,也就是只有 incr(增加) 和 reset(归0) 两种行为。而做了 sum() 或其他聚合之后,得到的就不再是一个 Counter 了,举个例子,比如 sum() 的计算对象中有一个归0了,那整体的和会下降,而不是归零,这会影响 rate() 中判断 reset(归0) 的逻辑,从而导致错误的结果。写 PromQL 时这个坑容易避免,但碰到 Recording Rule 就不那么容易了,因为不去看配置的话大家也想不到 new_metric 是怎么来的。

要完全规避这个坑,可以遵守一个原则:

Recording Rule 一步到位,直接算出需要的值,避免算出一个中间结果再拿去做聚合。

8、Alertmanager 的 group_interval 会影响 resolved 通知
Alertmanager 里有一个叫 group_interval 的配置,用于控制同一个 group 内的警报最快多久通知一次。

这里有一个问题是 firing(激活) 和 resolved(已消除) 的警报通知是共享同一个 group 的。
也就是说,假设我们的 group_interval 是默认的 5 分钟,那么一条警报激活十几秒后立马就消除了,它的消除通知会在报警通知的 5 分钟之后才到,因为在发完报警通知之后,这个 Group 需要等待 5 分钟的 group_interval 才能进行下一次通知。

这个设计让”警报消除就立马发送消除通知”变得几乎不可能,因为假如把 group_interval 变得很小的话,警报通知就会过于频繁,而调大的话,就会拖累到消除通知。

9、metrics和label命名
官方最佳实践:

https://prometheus.io/docs/practices/naming/
1
10、metrics name是特殊的label
metric name 也是一个 “label”

node_network_receive_packets_total{device=“bond0”} 本质上是

{__name__="node_network_receive_packets_total", device="bond0"} 。
1
但是因为 metric name 基本上是必用的 label,所以我们一般用第一种写法, 这样看起来更易懂