Prometheus,是由SoundCloud开发的开源监控系统,2016年加入CNCF(云原生计算基金会),是继Kubernetes之后,第二个CNCF毕业项目。

它,以强大的多维时序数据模型、灵活的PromQL查询语言、高效的本地存储、简单的部署方式,成为了云原生时代,监控的标配。现在,几乎所有的云原生项目,都原生支持Prometheus的监控指标,Kubernetes、Docker、Etcd、Istio等,都内置了Prometheus的metrics接口。

我们公司,从2017年开始,使用Prometheus,做监控系统。最开始,只有一台Prometheus服务器,监控几百个目标,几千个指标,完全够用。但是,随着业务的发展,监控的目标,越来越多,指标,越来越多,单机Prometheus,慢慢遇到了性能瓶颈。查询变慢了,内存不够用了,磁盘空间不够了,而且,单点故障,风险很大。

于是,我们开始,对Prometheus的监控架构,进行改造和优化。从单机,到高可用,到联邦集群,到长期存储,一步步,搭建了一套,高可用、高并发、可扩展的Prometheus监控架构。

今天,我想分享一下,我们在大规模场景下,Prometheus监控架构的设计经验。包括,单机Prometheus的局限性,高可用架构,联邦集群,长期存储,性能优化,以及一些踩坑经验。希望能给正在使用Prometheus,或者准备使用Prometheus的朋友,一些参考和启发。

一、单机Prometheus的局限性

在说架构设计之前,先说说,单机Prometheus,有哪些局限性。

Prometheus,默认是单机架构,一个Prometheus实例,负责采集、存储、查询所有的监控数据。这种架构,部署简单,维护方便,适合小规模的监控场景。但是,当监控规模,扩大到一定程度,单机Prometheus,就会遇到很多问题。

1. 性能瓶颈

Prometheus,是单进程的,虽然,它的性能,已经很强了,但是,单机的性能,总是有限的。当监控的目标,越来越多(比如,几千个目标),指标,越来越多(比如,几百万个时间序列),Prometheus的采集、存储、查询,都会变慢。

特别是,查询性能。Prometheus的本地存储,是基于LevelDB的,虽然,做了很多优化,但是,当数据量很大的时候,复杂的PromQL查询,还是会很慢,甚至,会超时。而且,Prometheus的查询,是单线程的(虽然,2.x版本,做了一些并行优化),复杂查询,会占用大量的CPU和内存。

2. 单点故障

单机Prometheus,最大的问题,就是单点故障。如果Prometheus服务器,宕机了,或者,磁盘坏了,那么,整个监控系统,就瘫痪了。你看不到监控数据,收不到告警,不知道系统的状态。这,对于生产环境来说,是不可接受的。

而且,Prometheus的升级、配置变更,也需要重启,重启的过程中,监控是不可用的。虽然,重启时间,一般不长,但是,对于关键业务来说,哪怕几分钟的监控不可用,也是风险。

3. 存储限制

Prometheus的本地存储,虽然,性能很好,但是,它是单机存储,容量,受限于单机的磁盘。而且,Prometheus的本地存储,设计目标,是短期存储(几周的数据),不是长期存储。如果你需要,保存几个月,甚至几年的监控数据,那么,本地存储,就不太合适了。

而且,本地存储,数据,只存在一台机器上,如果磁盘坏了,数据,就丢了。虽然,Prometheus支持,快照和备份,但是,备份,总是有延迟的,不可能做到,零数据丢失。

4. 扩展困难

单机Prometheus,扩展,很困难。当监控规模,超过了单机的能力,你不能,简单地,加一台机器,就扩展了。因为,Prometheus的采集、存储、查询,都是耦合在一个进程里的。你需要,手动地,把监控目标,拆分到多个Prometheus实例上,然后,用联邦,或者,其他方式,把数据,聚合起来。这个过程,比较复杂,也比较麻烦。

正是因为,单机Prometheus,有这些局限性,所以,当监控规模,扩大到一定程度,我们就需要,设计更复杂的Prometheus架构,来解决这些问题。

二、高可用架构

解决单点故障,最直接的方式,就是高可用。Prometheus的高可用,比较简单,因为,Prometheus,是无状态的(虽然,有本地存储,但是,数据,可以重复采集)。所以,我们可以,部署两个,或者多个,完全一样的Prometheus实例,采集相同的目标,存储相同的数据。这样,当一个实例,宕机了,另一个实例,还能正常工作,提供监控数据。

1. 主备模式

最简单的高可用,是主备模式。部署两个Prometheus实例,一个主,一个备。两个实例,采集相同的目标,存储相同的数据。平时,查询和告警,都走主实例。当主实例,宕机了,就切换到备实例。

这种模式,优点是,简单,容易实现。缺点是,备实例,平时,是闲置的,资源,有点浪费。而且,切换,需要手动,或者,用第三方工具(比如,Keepalived、HAProxy),自动切换。

2. 双活模式

更好的高可用,是双活模式。部署两个,或者多个,Prometheus实例,所有实例,都是活跃的,都在采集,都在存储。查询的时候,可以查询任意一个实例。告警的时候,所有实例,都在评估告警规则,但是,需要去重,避免,重复发送告警。

这种模式,优点是,没有闲置的资源,所有实例,都在工作。而且,没有切换的过程,任何一个实例,宕机了,其他实例,还能正常工作。缺点是,需要,在查询和告警的时候,做负载均衡和去重,稍微复杂一点。

我们用的,就是双活模式。我们部署了两个Prometheus实例,采集相同的目标。前面,用Nginx,做负载均衡,查询请求,轮询到两个实例上。告警,两个实例,都在评估,但是,我们用Alertmanager的高可用集群,做告警去重,确保,同一个告警,只发送一次。

3. Alertmanager高可用

除了Prometheus本身,Alertmanager,也需要高可用。Alertmanager,负责,接收Prometheus的告警,然后,去重、分组、路由、发送。如果Alertmanager,是单机的,那么,它宕机了,告警,就发不出去了。

Alertmanager,本身,支持集群模式。你可以,部署多个Alertmanager实例,组成一个集群。Prometheus,把告警,发送到所有的Alertmanager实例上。Alertmanager集群,通过Gossip协议,同步告警状态,确保,同一个告警,只被处理一次,只发送一次。

我们部署了三个Alertmanager实例,组成集群。Prometheus,把告警,发送到这三个实例上。Alertmanager集群,自动去重,自动分组,自动路由,发送告警。这样,即使,一个Alertmanager实例,宕机了,其他实例,还能正常工作,告警,不会丢失。

4. 高可用的注意事项

Prometheus高可用,有几个需要注意的地方:

第一,数据一致性。两个Prometheus实例,虽然,采集相同的目标,但是,因为,采集时间,可能有细微的差别,所以,数据,不可能完全一致。但是,对于监控来说,这种细微的差别,是可以接受的。查询的时候,查询任意一个实例,都可以。

第二,告警去重。双活模式下,两个Prometheus实例,都会评估告警规则,都会发送告警。所以,必须,用Alertmanager集群,做告警去重。否则,同一个告警,会发送两次,造成骚扰。

第三,配置同步。两个Prometheus实例,配置文件,必须一致。否则,一个实例,采集这个目标,另一个实例,采集那个目标,就乱了。我们用Ansible,管理配置文件,确保,所有实例的配置,都是一致的。

第四,时间同步。Prometheus,对时间,比较敏感。所有Prometheus实例,以及,被监控的目标,时间,必须同步。否则,可能会出现,数据错乱,告警异常等问题。我们用NTP,同步所有服务器的时间。

三、联邦集群

高可用,解决了单点故障的问题,但是,没有解决,性能和扩展的问题。当监控规模,很大的时候,即使,每个Prometheus实例,都是高可用的,但是,单个实例,还是会遇到性能瓶颈。这时候,就需要,用联邦集群,来扩展。

联邦(Federation),是Prometheus,内置的一个功能。它允许,一个Prometheus实例,从另一个Prometheus实例,抓取数据。这样,我们可以,把监控目标,拆分到多个Prometheus实例上,每个实例,只负责,一部分目标的采集和存储。然后,用一个,或者多个,联邦Prometheus实例,从各个子Prometheus实例上,抓取聚合后的数据,用于全局查询和告警。

1. 分层联邦

最常见的联邦架构,是分层联邦。一般,分为两层:底层,是多个数据中心(或者,多个业务线)的Prometheus实例,每个实例,负责,采集和存储,本数据中心(或者,本业务线)的监控数据。顶层,是一个,或者多个,联邦Prometheus实例,从底层的各个Prometheus实例上,抓取聚合后的,关键指标,用于全局查询和告警。

这种架构,优点是,扩展性好。每个底层Prometheus实例,只负责,一部分目标,性能压力,小了很多。而且,可以,按数据中心,或者,按业务线,横向扩展。顶层联邦实例,只抓取,聚合后的关键指标,数据量,不大,性能压力,也不大。

缺点是,顶层联邦实例,只有,聚合后的关键指标,没有,所有的原始指标。所以,如果你需要,查询某个具体的,细粒度的指标,你需要,直接查询,对应的底层Prometheus实例。

我们用的,就是这种分层联邦架构。我们有,三个数据中心,每个数据中心,部署了,两个高可用的Prometheus实例,负责,采集本数据中心的所有监控数据。然后,在核心数据中心,部署了,两个高可用的联邦Prometheus实例,从各个数据中心的Prometheus实例上,抓取聚合后的关键指标(比如,服务的QPS、延迟、错误率,服务器的CPU、内存、磁盘等),用于全局的Dashboard和告警。

2. 联邦的注意事项

联邦集群,有几个需要注意的地方:

第一,只抓取必要的指标。联邦,不要抓取,所有的原始指标。否则,顶层联邦实例,数据量,会非常大,性能压力,也会非常大。应该,只抓取,聚合后的,必要的关键指标。用PromQL的sumavgmax等函数,在底层实例上,先聚合,然后,联邦实例,再抓取聚合后的结果。

第二,合理设置采集间隔。联邦的采集间隔,不需要,和底层实例的采集间隔,一样短。可以,设置得长一些,比如,30秒,或者,1分钟。因为,联邦实例,主要用于,全局的趋势和告警,不需要,太细的粒度。采集间隔长一些,可以减轻,联邦实例的性能压力。

第三,注意网络延迟。如果,底层实例,和联邦实例,在不同的数据中心,那么,网络延迟,可能会比较大。联邦抓取,可能会超时,或者,数据延迟。所以,要确保,数据中心之间的网络,稳定,低延迟。或者,可以,在每个数据中心,都部署联邦实例,然后,再用更高层的联邦,聚合。

第四,避免联邦嵌套太深。联邦,不要嵌套太深,比如,三层、四层联邦。嵌套越深,架构,越复杂,维护,越困难,数据延迟,也越大。一般,两层联邦,就够了。如果,监控规模,特别大,可以考虑,用其他的,更高级的架构,比如,Thanos、Cortex等。

四、长期存储

Prometheus的本地存储,适合短期存储,但是,如果你需要,长期保存监控数据(几个月,甚至几年),那么,就需要,用长期存储方案。

Prometheus,从2.x版本开始,支持,远程读写(Remote Read/Write)接口。通过这个接口,Prometheus,可以,把数据,写到远程的存储系统中,也可以,从远程存储系统中,读取数据。这样,我们就可以,用专门的时序数据库,或者,对象存储,来长期保存,Prometheus的监控数据。

目前,比较流行的,Prometheus长期存储方案,有以下几种:

1. Thanos

Thanos,是由Improbable开源的,Prometheus高可用和长期存储方案。它,通过Sidecar的方式,和Prometheus,一起部署。Sidecar,负责,把Prometheus的本地数据,上传到对象存储(比如,S3、GCS、Azure Blob等),长期保存。同时,Thanos,提供了,一个全局的查询层(Querier),可以,同时查询,多个Prometheus实例的本地数据,以及,对象存储中的历史数据,实现了,全局的,无缝的查询体验。

Thanos,还支持,数据降采样(Downsampling)。它可以,把原始的,高分辨率的数据,降采样成,低分辨率的数据(比如,5分钟、1小时的分辨率),这样,查询长期数据的时候,速度,会快很多。

Thanos,是目前,最流行的,Prometheus长期存储方案之一。它,架构清晰,功能强大,社区活跃,已经,被很多公司,在生产环境中使用。

2. Cortex

Cortex,是由Weaveworks开源的,Prometheus水平扩展和长期存储方案。它,和Thanos,不太一样。Thanos,是基于,Sidecar和对象存储的,而Cortex,是一个,完全独立的,分布式时序数据库,兼容Prometheus的远程读写接口。

Cortex,把Prometheus的样本数据,分片,存储到,多个节点上,实现了,水平扩展。它,支持,多种后端存储,比如,DynamoDB、Bigtable、Cassandra、S3等。Cortex,还支持,多租户,数据隔离,非常适合,SaaS场景。

Cortex,也是一个,非常优秀的,Prometheus长期存储方案。它,性能好,扩展性强,支持多租户,适合,大规模的,多租户的监控场景。

3. VictoriaMetrics

VictoriaMetrics,是一个,高性能的,成本优化的时序数据库,兼容Prometheus的远程读写接口,以及,PromQL查询语言。它,既可以,作为Prometheus的长期存储,也可以,作为,独立的监控系统,直接采集和存储监控数据。

VictoriaMetrics,最大的特点,就是,高性能,低成本。它,在数据压缩、查询性能、资源占用等方面,都做得非常好。官方说,它,比Prometheus的本地存储,占用,少7倍的磁盘空间,查询性能,也更好。而且,它,部署简单,维护方便,单节点,就能处理,大量的数据。

VictoriaMetrics,是一个,新兴的,但是,非常有潜力的,时序数据库。如果你需要,简单、高效、低成本的,Prometheus长期存储方案,VictoriaMetrics,是一个,很好的选择。

4. 我们的选择

我们公司,用的长期存储方案,是Thanos。我们在,每个数据中心的Prometheus实例旁边,都部署了,Thanos Sidecar。Sidecar,负责,把Prometheus的本地数据,上传到,对象存储(我们用的,是MinIO,自建的S3兼容对象存储),长期保存。然后,在核心数据中心,部署了,Thanos Querier,作为,全局的查询层。Grafana,直接查询,Thanos Querier,就可以,同时查询,所有Prometheus实例的实时数据,以及,对象存储中的历史数据,体验,非常好。

Thanos,还帮我们,解决了,全局查询的问题。之前,用联邦的时候,顶层联邦实例,只有,聚合后的关键指标,查询细粒度的指标,需要,直接查询底层实例。用了Thanos之后,Thanos Querier,可以,同时查询,所有底层实例的,所有指标,不需要,再手动切换了。而且,Thanos,还支持,数据降采样,查询长期数据的时候,速度,很快。

当然,Thanos,也不是完美的。它,组件比较多(Sidecar、Store、Querier、Compactor、Ruler等),架构,比较复杂,维护,有一定的门槛。而且,对象存储的成本,也需要考虑。但是,总体来说,Thanos,是一个,非常优秀的,Prometheus长期存储和高可用方案,值得推荐。

五、性能优化

除了,架构设计,性能优化,也很重要。即使,架构设计得再好,如果,性能优化做得不好,监控系统,还是会,很慢,很卡。

下面,分享一些,我们在Prometheus性能优化方面,积累的经验:

1. 合理设置采集间隔

采集间隔,对Prometheus的性能,影响很大。采集间隔越短,数据量,越大,存储和查询的压力,也越大。所以,要根据,指标的重要性,合理设置采集间隔。

对于,关键的,变化快的指标(比如,服务的QPS、延迟、错误率),可以设置,短一些的采集间隔,比如,15秒,或者,30秒。对于,不那么关键的,变化慢的指标(比如,服务器的磁盘容量、网络流量、硬件温度),可以设置,长一些的采集间隔,比如,1分钟,或者,5分钟。

不要,所有的指标,都用,15秒的采集间隔。那样,数据量,会非常大,浪费存储空间,也增加,查询的压力。

2. 减少无用的指标

很多时候,Prometheus的性能问题,不是因为,监控的目标多,而是因为,无用的指标,太多了。很多应用,暴露了,几百个,甚至几千个指标,但是,真正用到的,可能,只有几十个。其他的,都是无用的,白白浪费,存储空间和查询性能。

所以,要定期,梳理指标,把无用的指标,去掉。可以,在Prometheus的配置中,用metricrelabelconfigs,丢弃,不需要的指标。也可以,和开发团队沟通,在应用层面,去掉,不需要暴露的指标。

减少无用的指标,是最有效的,性能优化手段之一。有时候,去掉一半的无用指标,Prometheus的性能,就能提升,一倍以上。

3. 合理使用标签

Prometheus的标签(Label),是非常强大的,但是,也是,性能杀手。如果,标签的基数(Cardinality)太高,那么,时间序列的数量,会爆炸式增长,Prometheus的内存和存储,都会,压力很大。

比如,如果你给一个指标,加了,用户ID、请求ID、IP地址等,高基数的标签,那么,这个指标,可能会有,几百万,甚至几千万个时间序列。Prometheus,根本,处理不了。

所以,要合理使用标签。标签,应该,用于,维度较低的,用于聚合和分组的属性,比如,服务名、实例名、数据中心、环境、状态码等。不要,用高基数的属性,作为标签。如果,需要记录,高基数的信息(比如,用户ID、请求ID),应该,用日志,而不是,监控指标。

还要,定期,检查,高基数的指标。Prometheus,提供了,一些API,可以查询,时间序列的数量,标签的基数。比如,/api/v1/status/tsdb,可以查看,TSDB的状态,包括,时间序列的数量,各个指标的序列数。定期检查,发现,高基数的指标,及时优化。

4. 优化PromQL查询

PromQL查询,对Prometheus的性能,影响也很大。特别是,复杂的,长时间范围的查询,会占用,大量的CPU和内存,甚至,会把Prometheus,搞挂。

所以,要优化PromQL查询。一些优化建议:

  • 避免,查询,太长时间范围的数据。比如,不要,一次查询,一年的数据。如果,需要长期趋势,可以用,降采样后的数据。
  • 避免,使用,太复杂的,嵌套的查询。复杂的查询,性能,会很差。尽量,把复杂的查询,拆分成,简单的查询,或者,用记录规则(Recording Rule),预先计算,常用的聚合指标。
  • 合理使用,范围向量和聚合函数。聚合函数(sum、avg、max等),可以,减少,返回的数据量,提高查询性能。
  • 用Grafana的变量,动态调整,查询的时间范围和步长。不要,固定,很小的步长,查询,很长的时间范围。

记录规则(Recording Rule),是一个,非常好的,查询优化手段。对于,常用的,复杂的聚合查询,可以,用记录规则,预先计算,结果,存储为,新的指标。这样,查询的时候,直接查询,预先计算好的指标,速度,会快很多。

5. 硬件优化

最后,硬件优化,也很重要。Prometheus,对CPU、内存、磁盘,都有,比较高的要求。

  • CPU:Prometheus的采集、压缩、查询,都需要,大量的CPU。特别是,查询,CPU密集型。所以,要给Prometheus,足够的CPU核心。一般,建议,至少4核,大规模的话,8核,16核,甚至更多。
  • 内存:Prometheus,会把,最近的数据,以及,索引,缓存在内存中。时间序列越多,内存,占用,越大。所以,要给Prometheus,足够的内存。一般,建议,至少8GB,大规模的话,16GB,32GB,甚至更多。
  • 磁盘:Prometheus的本地存储,对磁盘的IOPS,要求比较高。特别是,写入,非常频繁。所以,建议,用SSD,而不是,机械硬盘。而且,磁盘空间,要足够大,根据,数据量和保留时间,合理规划。

我们的Prometheus服务器,用的是,16核CPU,32GB内存,1TB SSD。对于,我们的监控规模(几千个目标,几百万个时间序列),基本够用。

六、踩坑经验

最后,分享一些,我们在Prometheus架构设计和使用过程中,踩过的坑。

坑一:时间不同步,导致数据错乱

有一次,我们发现,Prometheus的监控数据,经常,出现,数据点缺失,或者,数据错乱的情况。查了很久,才发现,是因为,有几台服务器,时间,没有同步,比标准时间,慢了,几分钟。Prometheus,采集的时候,因为,时间戳,不对,导致,数据,被丢弃,或者,错乱。

解决方法:所有服务器,包括,Prometheus服务器,和,被监控的目标,都必须,配置NTP,同步时间。而且,要定期,检查,时间同步的状态,确保,时间,是准确的。

坑二:标签基数太高,导致Prometheus OOM

有一次,我们上线了,一个新的服务,这个服务,暴露了一个指标,带了,用户ID标签。结果,上线后,没多久,Prometheus的内存,就暴涨,然后,OOM(内存溢出),宕机了。

查了一下,才发现,这个指标,因为,带了用户ID标签,有,几百万个时间序列。Prometheus的内存,根本,扛不住。

解决方法:去掉,用户ID标签。用户ID,是高基数的,不适合,作为监控标签。如果,需要按用户维度统计,应该,在应用层面,或者,日志系统中,处理,而不是,用Prometheus监控。

而且,我们还加了,限制。在Prometheus的配置中,设置了,sample_limit,限制,每个目标,最多,采集多少个样本。如果,超过了限制,就,丢弃这个目标的所有样本,并且,触发告警。这样,可以避免,某个应用,暴露了,太多的指标,把整个Prometheus,搞挂。

坑三:告警风暴,把告警通道,搞挂了

有一次,我们的一个核心服务,出了故障,导致,大量的告警,被触发。因为,我们的告警规则,设置得,不够合理,很多相关的告警,都被触发了,而且,没有,合理的分组和抑制。结果,几分钟之内,就发送了,几千条告警,把,短信通道,邮件服务器,都搞挂了。

解决方法:优化,告警规则和Alertmanager配置。

  • 合理设置,告警的阈值和持续时间。不要,太敏感,避免,频繁触发告警。
  • 合理分组。把,相关的告警,分到,同一个组里,一起发送,而不是,一条一条发。
  • 合理抑制。设置,抑制规则,当,高级别的告警,触发时,抑制,低级别的,相关的告警。比如,当,整个服务,宕机时,抑制,各个实例的告警。
  • 合理设置,重复发送的间隔。不要,太频繁地,重复发送,同一个告警。

优化之后,即使,出现大故障,告警的数量,也在,可控的范围内,不会,再出现,告警风暴了。

坑四:联邦抓取,超时,导致数据缺失

有一次,我们发现,顶层联邦实例,经常,出现,数据缺失的情况。查了一下,才发现,是因为,联邦抓取,超时了。底层Prometheus实例,因为,查询压力大,响应,很慢,联邦抓取,超过了,超时时间,就失败了,导致,数据缺失。

解决方法:

  • 优化,底层Prometheus实例的性能,提高,查询响应速度。
  • 合理设置,联邦的采集间隔和超时时间。采集间隔,不要太短,超时时间,要足够长。
  • 联邦,只抓取,必要的,聚合后的指标,不要抓取,所有的原始指标。这样,可以减轻,底层实例的查询压力,提高,响应速度。

优化之后,联邦抓取,就很少,超时了,数据,也比较完整了。

七、写在最后

Prometheus,是一个,非常优秀的,监控系统。它,简单,高效,灵活,功能强大,是云原生时代,监控的标配。

但是,随着,监控规模的扩大,单机Prometheus,会遇到,性能瓶颈、单点故障、存储限制等问题。这时候,就需要,设计,更复杂的架构,来解决这些问题。高可用,解决单点故障;联邦集群,解决性能和扩展;长期存储,解决数据保存;性能优化,让监控,更快,更稳。

我们公司,用了一年多的时间,一步步,把Prometheus的监控架构,从单机,改造到,高可用、联邦集群、长期存储。这个过程,踩了很多坑,也积累了很多经验。现在,我们的监控系统,稳定,高效,可扩展,能够,支撑,我们业务的快速发展。

当然,Prometheus的架构设计,没有,最好的,只有,最适合的。不同的公司,不同的监控规模,不同的需求,适合的架构,也不一样。不要,盲目追求,复杂的架构,也不要,盲目跟风,用最新的技术。要根据,自己的实际情况,选择,最适合的架构。

而且,监控,不是,一蹴而就的,是一个,持续优化,持续改进的过程。随着,业务的发展,监控的需求,也会变化。要定期,回顾,监控系统的架构和性能,发现问题,及时优化,持续改进。

最后,用一句话来结束这篇文章:"Prometheus,简单而强大。从小规模的单机,到大规模的集群,它,都能,很好地胜任。关键是,要根据,自己的需求,设计,合适的架构,持续优化,持续改进。"

希望这篇文章,能给正在使用Prometheus,或者准备使用Prometheus的朋友,一些参考和启发。如果你有不同的观点,或者,更好的经验,欢迎在评论区留言,我们一起交流。